Free cookie consent management tool by TermsFeed Policy Generator

source: branches/HeuristicLab.TimeSeries/HeuristicLab.Problems.DataAnalysis.Symbolic/3.4/SymbolicDataAnalysisExpressionTreeInterpreter.cs @ 7615

Last change on this file since 7615 was 7615, checked in by gkronber, 13 years ago

#1081 merged r7462:7609 from trunk into time series branch

File size: 23.3 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2012 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections.Generic;
24using System.Linq;
25using HeuristicLab.Common;
26using HeuristicLab.Core;
27using HeuristicLab.Data;
28using HeuristicLab.Encodings.SymbolicExpressionTreeEncoding;
29using HeuristicLab.Parameters;
30using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
31
32namespace HeuristicLab.Problems.DataAnalysis.Symbolic {
33  [StorableClass]
34  [Item("SymbolicDataAnalysisExpressionTreeInterpreter", "Interpreter for symbolic expression trees including automatically defined functions.")]
35  public sealed class SymbolicDataAnalysisExpressionTreeInterpreter : ParameterizedNamedItem,
36    ISymbolicDataAnalysisExpressionTreeInterpreter, ISymbolicTimeSeriesPrognosisExpressionTreeInterpreter {
37    private const string CheckExpressionsWithIntervalArithmeticParameterName = "CheckExpressionsWithIntervalArithmetic";
38    private const string EvaluatedSolutionsParameterName = "EvaluatedSolutions";
39    #region private classes
40    private class InterpreterState {
41      private double[] argumentStack;
42      private int argumentStackPointer;
43      private Instruction[] code;
44      private int pc;
45      public int ProgramCounter {
46        get { return pc; }
47        set { pc = value; }
48      }
49      internal InterpreterState(Instruction[] code, int argumentStackSize) {
50        this.code = code;
51        this.pc = 0;
52        if (argumentStackSize > 0) {
53          this.argumentStack = new double[argumentStackSize];
54        }
55        this.argumentStackPointer = 0;
56      }
57
58      internal void Reset() {
59        this.pc = 0;
60        this.argumentStackPointer = 0;
61      }
62
63      internal Instruction NextInstruction() {
64        return code[pc++];
65      }
66      private void Push(double val) {
67        argumentStack[argumentStackPointer++] = val;
68      }
69      private double Pop() {
70        return argumentStack[--argumentStackPointer];
71      }
72
73      internal void CreateStackFrame(double[] argValues) {
74        // push in reverse order to make indexing easier
75        for (int i = argValues.Length - 1; i >= 0; i--) {
76          argumentStack[argumentStackPointer++] = argValues[i];
77        }
78        Push(argValues.Length);
79      }
80
81      internal void RemoveStackFrame() {
82        int size = (int)Pop();
83        argumentStackPointer -= size;
84      }
85
86      internal double GetStackFrameValue(ushort index) {
87        // layout of stack:
88        // [0]   <- argumentStackPointer
89        // [StackFrameSize = N + 1]
90        // [Arg0] <- argumentStackPointer - 2 - 0
91        // [Arg1] <- argumentStackPointer - 2 - 1
92        // [...]
93        // [ArgN] <- argumentStackPointer - 2 - N
94        // <Begin of stack frame>
95        return argumentStack[argumentStackPointer - index - 2];
96      }
97    }
98    private class OpCodes {
99      public const byte Add = 1;
100      public const byte Sub = 2;
101      public const byte Mul = 3;
102      public const byte Div = 4;
103
104      public const byte Sin = 5;
105      public const byte Cos = 6;
106      public const byte Tan = 7;
107
108      public const byte Log = 8;
109      public const byte Exp = 9;
110
111      public const byte IfThenElse = 10;
112
113      public const byte GT = 11;
114      public const byte LT = 12;
115
116      public const byte AND = 13;
117      public const byte OR = 14;
118      public const byte NOT = 15;
119
120
121      public const byte Average = 16;
122
123      public const byte Call = 17;
124
125      public const byte Variable = 18;
126      public const byte LagVariable = 19;
127      public const byte Constant = 20;
128      public const byte Arg = 21;
129
130      public const byte Power = 22;
131      public const byte Root = 23;
132      public const byte TimeLag = 24;
133      public const byte Integral = 25;
134      public const byte Derivative = 26;
135
136      public const byte VariableCondition = 27;
137    }
138    #endregion
139
140    private Dictionary<Type, byte> symbolToOpcode = new Dictionary<Type, byte>() {
141      { typeof(Addition), OpCodes.Add },
142      { typeof(Subtraction), OpCodes.Sub },
143      { typeof(Multiplication), OpCodes.Mul },
144      { typeof(Division), OpCodes.Div },
145      { typeof(Sine), OpCodes.Sin },
146      { typeof(Cosine), OpCodes.Cos },
147      { typeof(Tangent), OpCodes.Tan },
148      { typeof(Logarithm), OpCodes.Log },
149      { typeof(Exponential), OpCodes.Exp },
150      { typeof(IfThenElse), OpCodes.IfThenElse },
151      { typeof(GreaterThan), OpCodes.GT },
152      { typeof(LessThan), OpCodes.LT },
153      { typeof(And), OpCodes.AND },
154      { typeof(Or), OpCodes.OR },
155      { typeof(Not), OpCodes.NOT},
156      { typeof(Average), OpCodes.Average},
157      { typeof(InvokeFunction), OpCodes.Call },
158      { typeof(HeuristicLab.Problems.DataAnalysis.Symbolic.Variable), OpCodes.Variable },
159      { typeof(LaggedVariable), OpCodes.LagVariable },
160      { typeof(Constant), OpCodes.Constant },
161      { typeof(Argument), OpCodes.Arg },
162      { typeof(Power),OpCodes.Power},
163      { typeof(Root),OpCodes.Root},
164      { typeof(TimeLag), OpCodes.TimeLag},
165      { typeof(Integral), OpCodes.Integral},
166      { typeof(Derivative), OpCodes.Derivative},
167      { typeof(VariableCondition),OpCodes.VariableCondition}
168    };
169
170    public override bool CanChangeName {
171      get { return false; }
172    }
173    public override bool CanChangeDescription {
174      get { return false; }
175    }
176
177    #region parameter properties
178    public IValueParameter<BoolValue> CheckExpressionsWithIntervalArithmeticParameter {
179      get { return (IValueParameter<BoolValue>)Parameters[CheckExpressionsWithIntervalArithmeticParameterName]; }
180    }
181
182    public IValueParameter<IntValue> EvaluatedSolutionsParameter {
183      get { return (IValueParameter<IntValue>)Parameters[EvaluatedSolutionsParameterName]; }
184    }
185    #endregion
186
187    #region properties
188    public BoolValue CheckExpressionsWithIntervalArithmetic {
189      get { return CheckExpressionsWithIntervalArithmeticParameter.Value; }
190      set { CheckExpressionsWithIntervalArithmeticParameter.Value = value; }
191    }
192
193    public IntValue EvaluatedSolutions {
194      get { return EvaluatedSolutionsParameter.Value; }
195      set { EvaluatedSolutionsParameter.Value = value; }
196    }
197    #endregion
198
199    [StorableConstructor]
200    private SymbolicDataAnalysisExpressionTreeInterpreter(bool deserializing) : base(deserializing) { }
201    private SymbolicDataAnalysisExpressionTreeInterpreter(SymbolicDataAnalysisExpressionTreeInterpreter original, Cloner cloner) : base(original, cloner) { }
202    public override IDeepCloneable Clone(Cloner cloner) {
203      return new SymbolicDataAnalysisExpressionTreeInterpreter(this, cloner);
204    }
205
206    public SymbolicDataAnalysisExpressionTreeInterpreter()
207      : base("SymbolicDataAnalysisExpressionTreeInterpreter", "Interpreter for symbolic expression trees including automatically defined functions.") {
208      Parameters.Add(new ValueParameter<BoolValue>(CheckExpressionsWithIntervalArithmeticParameterName, "Switch that determines if the interpreter checks the validity of expressions with interval arithmetic before evaluating the expression.", new BoolValue(false)));
209      Parameters.Add(new ValueParameter<IntValue>(EvaluatedSolutionsParameterName, "A counter for the total number of solutions the interpreter has evaluated", new IntValue(0)));
210    }
211
212    [StorableHook(HookType.AfterDeserialization)]
213    private void AfterDeserialization() {
214      if (!Parameters.ContainsKey(EvaluatedSolutionsParameterName))
215        Parameters.Add(new ValueParameter<IntValue>(EvaluatedSolutionsParameterName, "A counter for the total number of solutions the interpreter has evaluated", new IntValue(0)));
216    }
217
218    #region IStatefulItem
219    public void InitializeState() {
220      EvaluatedSolutions.Value = 0;
221    }
222
223    public void ClearState() {
224    }
225    #endregion
226
227    public IEnumerable<double> GetSymbolicExpressionTreeValues(ISymbolicExpressionTree tree, Dataset dataset, IEnumerable<int> rows) {
228      return GetSymbolicExpressionTreeValues(tree, dataset, new string[] { "#NOTHING#" }, rows);
229    }
230
231    public IEnumerable<double> GetSymbolicExpressionTreeValues(ISymbolicExpressionTree tree, Dataset dataset, string[] targetVariables, IEnumerable<int> rows) {
232      return GetSymbolicExpressionTreeValues(tree, dataset, targetVariables, rows, 1);
233    }
234
235
236    // for each row for each horizon for each target variable one value
237    public IEnumerable<double> GetSymbolicExpressionTreeValues(ISymbolicExpressionTree tree, Dataset dataset, string[] targetVariables, IEnumerable<int> rows, int horizon) {
238      if (CheckExpressionsWithIntervalArithmetic.Value)
239        throw new NotSupportedException("Interval arithmetic is not yet supported in the symbolic data analysis interpreter.");
240      EvaluatedSolutions.Value++; // increment the evaluated solutions counter
241      var compiler = new SymbolicExpressionTreeCompiler();
242      Instruction[] code = compiler.Compile(tree, MapSymbolToOpCode);
243      int necessaryArgStackSize = 0;
244      for (int i = 0; i < code.Length; i++) {
245        Instruction instr = code[i];
246        if (instr.opCode == OpCodes.Variable) {
247          var variableTreeNode = instr.dynamicNode as VariableTreeNode;
248          instr.iArg0 = dataset.GetReadOnlyDoubleValues(variableTreeNode.VariableName);
249          code[i] = instr;
250        } else if (instr.opCode == OpCodes.LagVariable) {
251          var laggedVariableTreeNode = instr.dynamicNode as LaggedVariableTreeNode;
252          instr.iArg0 = dataset.GetReadOnlyDoubleValues(laggedVariableTreeNode.VariableName);
253          code[i] = instr;
254        } else if (instr.opCode == OpCodes.VariableCondition) {
255          var variableConditionTreeNode = instr.dynamicNode as VariableConditionTreeNode;
256          instr.iArg0 = dataset.GetReadOnlyDoubleValues(variableConditionTreeNode.VariableName);
257        } else if (instr.opCode == OpCodes.Call) {
258          necessaryArgStackSize += instr.nArguments + 1;
259        }
260      }
261      var state = new InterpreterState(code, necessaryArgStackSize);
262
263      int nComponents = tree.Root.GetSubtree(0).SubtreeCount;
264      // produce a n-step forecast for each target variable for all rows
265      var cachedPrognosedValues = new Dictionary<string, double[]>();
266      foreach (var targetVariable in targetVariables)
267        cachedPrognosedValues[targetVariable] = new double[horizon];
268      foreach (var rowEnum in rows) {
269        int row = rowEnum;
270        foreach (var horizonRow in Enumerable.Range(row, horizon)) {
271          int localRow = horizonRow; // create a local variable for the ref parameter
272          for (int c = 0; c < nComponents; c++) {
273            var prog = Evaluate(dataset, ref localRow, row - 1, state, cachedPrognosedValues);
274            yield return prog;
275            cachedPrognosedValues[targetVariables[c]][horizonRow - row] = prog;
276          }
277
278          state.Reset();
279        }
280      }
281    }
282
283    private double Evaluate(Dataset dataset, ref int row, int lastObservedRow, InterpreterState state, Dictionary<string, double[]> cachedPrognosedValues) {
284      Instruction currentInstr = state.NextInstruction();
285      switch (currentInstr.opCode) {
286        case OpCodes.Add: {
287            double s = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
288            for (int i = 1; i < currentInstr.nArguments; i++) {
289              s += Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
290            }
291            return s;
292          }
293        case OpCodes.Sub: {
294            double s = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
295            for (int i = 1; i < currentInstr.nArguments; i++) {
296              s -= Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
297            }
298            if (currentInstr.nArguments == 1) s = -s;
299            return s;
300          }
301        case OpCodes.Mul: {
302            double p = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
303            for (int i = 1; i < currentInstr.nArguments; i++) {
304              p *= Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
305            }
306            return p;
307          }
308        case OpCodes.Div: {
309            double p = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
310            for (int i = 1; i < currentInstr.nArguments; i++) {
311              p /= Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
312            }
313            if (currentInstr.nArguments == 1) p = 1.0 / p;
314            return p;
315          }
316        case OpCodes.Average: {
317            double sum = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
318            for (int i = 1; i < currentInstr.nArguments; i++) {
319              sum += Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
320            }
321            return sum / currentInstr.nArguments;
322          }
323        case OpCodes.Cos: {
324            return Math.Cos(Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues));
325          }
326        case OpCodes.Sin: {
327            return Math.Sin(Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues));
328          }
329        case OpCodes.Tan: {
330            return Math.Tan(Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues));
331          }
332        case OpCodes.Power: {
333            double x = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
334            double y = Math.Round(Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues));
335            return Math.Pow(x, y);
336          }
337        case OpCodes.Root: {
338            double x = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
339            double y = Math.Round(Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues));
340            return Math.Pow(x, 1 / y);
341          }
342        case OpCodes.Exp: {
343            return Math.Exp(Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues));
344          }
345        case OpCodes.Log: {
346            return Math.Log(Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues));
347          }
348        case OpCodes.IfThenElse: {
349            double condition = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
350            double result;
351            if (condition > 0.0) {
352              result = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues); SkipInstructions(state);
353            } else {
354              SkipInstructions(state); result = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
355            }
356            return result;
357          }
358        case OpCodes.AND: {
359            double result = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
360            for (int i = 1; i < currentInstr.nArguments; i++) {
361              if (result > 0.0) result = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
362              else {
363                SkipInstructions(state);
364              }
365            }
366            return result > 0.0 ? 1.0 : -1.0;
367          }
368        case OpCodes.OR: {
369            double result = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
370            for (int i = 1; i < currentInstr.nArguments; i++) {
371              if (result <= 0.0) result = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
372              else {
373                SkipInstructions(state);
374              }
375            }
376            return result > 0.0 ? 1.0 : -1.0;
377          }
378        case OpCodes.NOT: {
379            return Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues) > 0.0 ? -1.0 : 1.0;
380          }
381        case OpCodes.GT: {
382            double x = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
383            double y = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
384            if (x > y) return 1.0;
385            else return -1.0;
386          }
387        case OpCodes.LT: {
388            double x = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
389            double y = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
390            if (x < y) return 1.0;
391            else return -1.0;
392          }
393        case OpCodes.TimeLag: {
394            var timeLagTreeNode = (LaggedTreeNode)currentInstr.dynamicNode;
395            row += timeLagTreeNode.Lag;
396            double result = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
397            row -= timeLagTreeNode.Lag;
398            return result;
399          }
400        case OpCodes.Integral: {
401            int savedPc = state.ProgramCounter;
402            var timeLagTreeNode = (LaggedTreeNode)currentInstr.dynamicNode;
403            double sum = 0.0;
404            for (int i = 0; i < Math.Abs(timeLagTreeNode.Lag); i++) {
405              row += Math.Sign(timeLagTreeNode.Lag);
406              sum += Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
407              state.ProgramCounter = savedPc;
408            }
409            row -= timeLagTreeNode.Lag;
410            sum += Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
411            return sum;
412          }
413
414        //mkommend: derivate calculation taken from:
415        //http://www.holoborodko.com/pavel/numerical-methods/numerical-derivative/smooth-low-noise-differentiators/
416        //one sided smooth differentiatior, N = 4
417        // y' = 1/8h (f_i + 2f_i-1, -2 f_i-3 - f_i-4)
418        case OpCodes.Derivative: {
419            int savedPc = state.ProgramCounter;
420            double f_0 = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues); row--;
421            state.ProgramCounter = savedPc;
422            double f_1 = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues); row -= 2;
423            state.ProgramCounter = savedPc;
424            double f_3 = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues); row--;
425            state.ProgramCounter = savedPc;
426            double f_4 = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
427            row += 4;
428
429            return (f_0 + 2 * f_1 - 2 * f_3 - f_4) / 8; // h = 1
430          }
431        case OpCodes.Call: {
432            // evaluate sub-trees
433            double[] argValues = new double[currentInstr.nArguments];
434            for (int i = 0; i < currentInstr.nArguments; i++) {
435              argValues[i] = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
436            }
437            // push on argument values on stack
438            state.CreateStackFrame(argValues);
439
440            // save the pc
441            int savedPc = state.ProgramCounter;
442            // set pc to start of function 
443            state.ProgramCounter = (ushort)currentInstr.iArg0;
444            // evaluate the function
445            double v = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
446
447            // delete the stack frame
448            state.RemoveStackFrame();
449
450            // restore the pc => evaluation will continue at point after my subtrees 
451            state.ProgramCounter = savedPc;
452            return v;
453          }
454        case OpCodes.Arg: {
455            return state.GetStackFrameValue((ushort)currentInstr.iArg0);
456          }
457        case OpCodes.Variable: {
458            if (row < 0 || row >= dataset.Rows)
459              return double.NaN;
460            var variableTreeNode = (VariableTreeNode)currentInstr.dynamicNode;
461            if (row <= lastObservedRow || !cachedPrognosedValues.ContainsKey(variableTreeNode.VariableName)) return ((IList<double>)currentInstr.iArg0)[row] * variableTreeNode.Weight;
462            else return cachedPrognosedValues[variableTreeNode.VariableName][row - lastObservedRow - 1] * variableTreeNode.Weight;
463          }
464        case OpCodes.LagVariable: {
465            var laggedVariableTreeNode = (LaggedVariableTreeNode)currentInstr.dynamicNode;
466            int actualRow = row + laggedVariableTreeNode.Lag;
467            if (actualRow < 0 || actualRow >= dataset.Rows)
468              return double.NaN;
469            if (actualRow <= lastObservedRow || !cachedPrognosedValues.ContainsKey(laggedVariableTreeNode.VariableName)) return ((IList<double>)currentInstr.iArg0)[actualRow] * laggedVariableTreeNode.Weight;
470            else return cachedPrognosedValues[laggedVariableTreeNode.VariableName][actualRow - lastObservedRow - 1] * laggedVariableTreeNode.Weight;
471          }
472        case OpCodes.Constant: {
473            var constTreeNode = currentInstr.dynamicNode as ConstantTreeNode;
474            return constTreeNode.Value;
475          }
476
477        //mkommend: this symbol uses the logistic function f(x) = 1 / (1 + e^(-alpha * x) )
478        //to determine the relative amounts of the true and false branch see http://en.wikipedia.org/wiki/Logistic_function
479        case OpCodes.VariableCondition: {
480            if (row < 0 || row >= dataset.Rows)
481              return double.NaN;
482            var variableConditionTreeNode = (VariableConditionTreeNode)currentInstr.dynamicNode;
483            double variableValue;
484            if (row <= lastObservedRow || !cachedPrognosedValues.ContainsKey(variableConditionTreeNode.VariableName))
485              variableValue = ((IList<double>)currentInstr.iArg0)[row];
486            else
487              variableValue = cachedPrognosedValues[variableConditionTreeNode.VariableName][row - lastObservedRow - 1];
488
489            double x = variableValue - variableConditionTreeNode.Threshold;
490            double p = 1 / (1 + Math.Exp(-variableConditionTreeNode.Slope * x));
491
492            double trueBranch = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
493            double falseBranch = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
494
495            return trueBranch * p + falseBranch * (1 - p);
496          }
497        default: throw new NotSupportedException();
498      }
499    }
500
501    private byte MapSymbolToOpCode(ISymbolicExpressionTreeNode treeNode) {
502      if (symbolToOpcode.ContainsKey(treeNode.Symbol.GetType()))
503        return symbolToOpcode[treeNode.Symbol.GetType()];
504      else
505        throw new NotSupportedException("Symbol: " + treeNode.Symbol);
506    }
507
508    // skips a whole branch
509    private void SkipInstructions(InterpreterState state) {
510      int i = 1;
511      while (i > 0) {
512        i += state.NextInstruction().nArguments;
513        i--;
514      }
515    }
516  }
517}
Note: See TracBrowser for help on using the repository browser.