Free cookie consent management tool by TermsFeed Policy Generator

source: branches/3040_VectorBasedGP/HeuristicLab.Problems.DataAnalysis.Symbolic/3.4/Importer/InfixExpressionParser.cs @ 17622

Last change on this file since 17622 was 17622, checked in by pfleck, 4 years ago

#3040 Added vector variables to infix parser/formatter.

File size: 23.9 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 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.Globalization;
25using System.Linq;
26using System.Text;
27using HeuristicLab.Collections;
28using HeuristicLab.Common;
29using HeuristicLab.Encodings.SymbolicExpressionTreeEncoding;
30
31using DoubleVector = MathNet.Numerics.LinearAlgebra.Vector<double>;
32
33namespace HeuristicLab.Problems.DataAnalysis.Symbolic {
34  /// <summary>
35  /// Parses mathematical expressions in infix form. E.g. x1 * (3.0 * x2 + x3)
36  /// Identifier format (functions or variables): '_' | letter { '_' | letter | digit }
37  /// Variables names and variable values can be set under quotes "" or '' because variable names might contain spaces.
38  ///   Variable = ident | " ident " | ' ident '
39  /// It is also possible to use functions e.g. log("x1") or real-valued constants e.g. 3.1415 .
40  /// Variable names are case sensitive. Function names are not case sensitive.
41  ///
42  ///
43  /// S             = Expr EOF
44  /// Expr          = ['-' | '+'] Term { '+' Term | '-' Term }
45  /// Term          = Fact { '*' Fact | '/' Fact }
46  /// Fact          = SimpleFact [ '^' SimpleFact ]
47  /// SimpleFact    = '(' Expr ')'
48  ///                 | '{' Expr '}'
49  ///                 | 'LAG' '(' varId ',' ['+' | '-' ] number ')
50  ///                 | funcId '(' ArgList ')'
51  ///                 | VarExpr
52  ///                 | number
53  /// ArgList       = Expr { ',' Expr }
54  /// VarExpr       = varId ( OptFactorPart | OptVectorPart )
55  /// OptFactorPart = [ ('=' varVal | '[' ['+' | '-' ] number {',' ['+' | '-' ] number } ']' ) ]
56  /// OptVectorPart = [ '{' [ number {',' number } ] '}' ]
57  /// varId         =  ident | ' ident ' | " ident "
58  /// varVal        =  ident | ' ident ' | " ident "
59  /// ident         =  '_' | letter { '_' | letter | digit }
60  /// </summary>
61  public sealed class InfixExpressionParser {
62    private enum TokenType { Operator, Identifier, Number, LeftPar, RightPar, LeftBracket, RightBracket, Comma, Eq, End, NA, LeftBrace, RightBrace };
63    private class Token {
64      internal double doubleVal;
65      internal string strVal;
66      internal TokenType TokenType;
67    }
68
69    private class SymbolNameComparer : IEqualityComparer<ISymbol>, IComparer<ISymbol> {
70      public int Compare(ISymbol x, ISymbol y) {
71        return x.Name.CompareTo(y.Name);
72      }
73
74      public bool Equals(ISymbol x, ISymbol y) {
75        return Compare(x, y) == 0;
76      }
77
78      public int GetHashCode(ISymbol obj) {
79        return obj.Name.GetHashCode();
80      }
81    }
82    // format name <-> symbol
83    // the lookup table is also used in the corresponding formatter
84    internal static readonly BidirectionalLookup<string, ISymbol>
85      knownSymbols = new BidirectionalLookup<string, ISymbol>(StringComparer.InvariantCulture, new SymbolNameComparer());
86
87    private Constant constant = new Constant();
88    private Variable variable = new Variable();
89    private BinaryFactorVariable binaryFactorVar = new BinaryFactorVariable();
90    private FactorVariable factorVar = new FactorVariable();
91    private Variable vectorVariable = new Variable() { VariableDataType = typeof(DoubleVector) };
92
93    private ProgramRootSymbol programRootSymbol = new ProgramRootSymbol();
94    private StartSymbol startSymbol = new StartSymbol();
95
96    static InfixExpressionParser() {
97      // populate bidirectional lookup
98      var dict = new Dictionary<string, ISymbol>
99      {
100        { "+", new Addition()},
101        { "/", new Division()},
102        { "*", new Multiplication()},
103        { "-", new Subtraction()},
104        { "^", new Power() },
105        { "ABS", new Absolute() },
106        { "EXP", new Exponential()},
107        { "LOG", new Logarithm()},
108        { "POW", new Power() },
109        { "ROOT", new Root()},
110        { "SQR", new Square() },
111        { "SQRT", new SquareRoot() },
112        { "CUBE", new Cube() },
113        { "CUBEROOT", new CubeRoot() },
114        { "SIN",new Sine()},
115        { "COS", new Cosine()},
116        { "TAN", new Tangent()},
117        { "TANH", new HyperbolicTangent()},
118        { "AIRYA", new AiryA()},
119        { "AIRYB", new AiryB()},
120        { "BESSEL", new Bessel()},
121        { "COSINT", new CosineIntegral()},
122        { "SININT", new SineIntegral()},
123        { "HYPCOSINT", new HyperbolicCosineIntegral()},
124        { "HYPSININT", new HyperbolicSineIntegral()},
125        { "FRESNELSININT", new FresnelSineIntegral()},
126        { "FRESNELCOSINT", new FresnelCosineIntegral()},
127        { "NORM", new Norm()},
128        { "ERF", new Erf()},
129        { "GAMMA", new Gamma()},
130        { "PSI", new Psi()},
131        { "DAWSON", new Dawson()},
132        { "EXPINT", new ExponentialIntegralEi()},
133        { "AQ", new AnalyticQuotient() },
134        { "MEAN", new Average()},
135        { "IF", new IfThenElse()},
136        { "GT", new GreaterThan()},
137        { "LT", new LessThan()},
138        { "AND", new And()},
139        { "OR", new Or()},
140        { "NOT", new Not()},
141        { "XOR", new Xor()},
142        { "DIFF", new Derivative()},
143        { "LAG", new LaggedVariable() },
144      };
145
146
147      foreach (var kvp in dict) {
148        knownSymbols.Add(kvp.Key, kvp.Value);
149      }
150    }
151
152    public ISymbolicExpressionTree Parse(string str) {
153      ISymbolicExpressionTreeNode root = programRootSymbol.CreateTreeNode();
154      ISymbolicExpressionTreeNode start = startSymbol.CreateTreeNode();
155      var allTokens = GetAllTokens(str).ToArray();
156      ISymbolicExpressionTreeNode mainBranch = ParseS(new Queue<Token>(allTokens));
157
158      // only a main branch was given => insert the main branch into the default tree template
159      root.AddSubtree(start);
160      start.AddSubtree(mainBranch);
161      return new SymbolicExpressionTree(root);
162    }
163
164    private IEnumerable<Token> GetAllTokens(string str) {
165      int pos = 0;
166      while (true) {
167        while (pos < str.Length && Char.IsWhiteSpace(str[pos])) pos++;
168        if (pos >= str.Length) {
169          yield return new Token { TokenType = TokenType.End, strVal = "" };
170          yield break;
171        }
172        if (char.IsDigit(str[pos])) {
173          // read number (=> read until white space or operator or comma)
174          var sb = new StringBuilder();
175          sb.Append(str[pos]);
176          pos++;
177          while (pos < str.Length && !char.IsWhiteSpace(str[pos])
178            && (str[pos] != '+' || str[pos - 1] == 'e' || str[pos - 1] == 'E')     // continue reading exponents
179            && (str[pos] != '-' || str[pos - 1] == 'e' || str[pos - 1] == 'E')
180            && str[pos] != '*'
181            && str[pos] != '/'
182            && str[pos] != '^'
183            && str[pos] != ')'
184            && str[pos] != ']'
185            && str[pos] != '}'
186            && str[pos] != ',') {
187            sb.Append(str[pos]);
188            pos++;
189          }
190          double dblVal;
191          if (double.TryParse(sb.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture, out dblVal))
192            yield return new Token { TokenType = TokenType.Number, strVal = sb.ToString(), doubleVal = dblVal };
193          else yield return new Token { TokenType = TokenType.NA, strVal = sb.ToString() };
194        } else if (char.IsLetter(str[pos]) || str[pos] == '_') {
195          // read ident
196          var sb = new StringBuilder();
197          sb.Append(str[pos]);
198          pos++;
199          while (pos < str.Length &&
200            (char.IsLetter(str[pos]) || str[pos] == '_' || char.IsDigit(str[pos]))) {
201            sb.Append(str[pos]);
202            pos++;
203          }
204          yield return new Token { TokenType = TokenType.Identifier, strVal = sb.ToString() };
205        } else if (str[pos] == '"') {
206          // read to next "
207          pos++;
208          var sb = new StringBuilder();
209          while (pos < str.Length && str[pos] != '"') {
210            sb.Append(str[pos]);
211            pos++;
212          }
213          if (pos < str.Length && str[pos] == '"') {
214            pos++; // skip "
215            yield return new Token { TokenType = TokenType.Identifier, strVal = sb.ToString() };
216          } else
217            yield return new Token { TokenType = TokenType.NA };
218
219        } else if (str[pos] == '\'') {
220          // read to next '
221          pos++;
222          var sb = new StringBuilder();
223          while (pos < str.Length && str[pos] != '\'') {
224            sb.Append(str[pos]);
225            pos++;
226          }
227          if (pos < str.Length && str[pos] == '\'') {
228            pos++; // skip '
229            yield return new Token { TokenType = TokenType.Identifier, strVal = sb.ToString() };
230          } else
231            yield return new Token { TokenType = TokenType.NA };
232        } else if (str[pos] == '+') {
233          pos++;
234          yield return new Token { TokenType = TokenType.Operator, strVal = "+" };
235        } else if (str[pos] == '-') {
236          pos++;
237          yield return new Token { TokenType = TokenType.Operator, strVal = "-" };
238        } else if (str[pos] == '/') {
239          pos++;
240          yield return new Token { TokenType = TokenType.Operator, strVal = "/" };
241        } else if (str[pos] == '*') {
242          pos++;
243          yield return new Token { TokenType = TokenType.Operator, strVal = "*" };
244        } else if (str[pos] == '^') {
245          pos++;
246          yield return new Token { TokenType = TokenType.Operator, strVal = "^" };
247        } else if (str[pos] == '(') {
248          pos++;
249          yield return new Token { TokenType = TokenType.LeftPar, strVal = "(" };
250        } else if (str[pos] == ')') {
251          pos++;
252          yield return new Token { TokenType = TokenType.RightPar, strVal = ")" };
253        } else if (str[pos] == '[') {
254          pos++;
255          yield return new Token { TokenType = TokenType.LeftBracket, strVal = "[" };
256        } else if (str[pos] == ']') {
257          pos++;
258          yield return new Token { TokenType = TokenType.RightBracket, strVal = "]" };
259        } else if (str[pos] == '{') {
260          pos++;
261          yield return new Token { TokenType = TokenType.LeftBrace, strVal = "{" };
262        } else if (str[pos] == '}') {
263          pos++;
264          yield return new Token { TokenType = TokenType.RightBrace, strVal = "}" };
265        } else if (str[pos] == '=') {
266          pos++;
267          yield return new Token { TokenType = TokenType.Eq, strVal = "=" };
268        } else if (str[pos] == ',') {
269          pos++;
270          yield return new Token { TokenType = TokenType.Comma, strVal = "," };
271        } else {
272          throw new ArgumentException("Invalid character: " + str[pos]);
273        }
274      }
275    }
276    /// S             = Expr EOF
277    private ISymbolicExpressionTreeNode ParseS(Queue<Token> tokens) {
278      var expr = ParseExpr(tokens);
279
280      var endTok = tokens.Dequeue();
281      if (endTok.TokenType != TokenType.End)
282        throw new ArgumentException(string.Format("Expected end of expression (got {0})", endTok.strVal));
283
284      return expr;
285    }
286
287    /// Expr          = ['-' | '+'] Term { '+' Term | '-' Term }
288    private ISymbolicExpressionTreeNode ParseExpr(Queue<Token> tokens) {
289      var next = tokens.Peek();
290      var posTerms = new List<ISymbolicExpressionTreeNode>();
291      var negTerms = new List<ISymbolicExpressionTreeNode>();
292      bool negateFirstTerm = false;
293      if (next.TokenType == TokenType.Operator && (next.strVal == "+" || next.strVal == "-")) {
294        tokens.Dequeue();
295        if (next.strVal == "-")
296          negateFirstTerm = true;
297      }
298      var t = ParseTerm(tokens);
299      if (negateFirstTerm) negTerms.Add(t);
300      else posTerms.Add(t);
301
302      next = tokens.Peek();
303      while (next.strVal == "+" || next.strVal == "-") {
304        switch (next.strVal) {
305          case "+": {
306              tokens.Dequeue();
307              var term = ParseTerm(tokens);
308              posTerms.Add(term);
309              break;
310            }
311          case "-": {
312              tokens.Dequeue();
313              var term = ParseTerm(tokens);
314              negTerms.Add(term);
315              break;
316            }
317        }
318        next = tokens.Peek();
319      }
320
321      var sum = GetSymbol("+").CreateTreeNode();
322      foreach (var posTerm in posTerms) sum.AddSubtree(posTerm);
323      if (negTerms.Any()) {
324        if (negTerms.Count == 1) {
325          var sub = GetSymbol("-").CreateTreeNode();
326          sub.AddSubtree(negTerms.Single());
327          sum.AddSubtree(sub);
328        } else {
329          var sumNeg = GetSymbol("+").CreateTreeNode();
330          foreach (var negTerm in negTerms) sumNeg.AddSubtree(negTerm);
331
332          var constNode = (ConstantTreeNode)constant.CreateTreeNode();
333          constNode.Value = -1.0;
334          var prod = GetSymbol("*").CreateTreeNode();
335          prod.AddSubtree(constNode);
336          prod.AddSubtree(sumNeg);
337
338          sum.AddSubtree(prod);
339        }
340      }
341      if (sum.SubtreeCount == 1) return sum.Subtrees.First();
342      else return sum;
343    }
344
345    private ISymbol GetSymbol(string tok) {
346      var symb = knownSymbols.GetByFirst(tok).FirstOrDefault();
347      if (symb == null) throw new ArgumentException(string.Format("Unknown token {0} found.", tok));
348      return symb;
349    }
350
351    /// Term          = Fact { '*' Fact | '/' Fact }
352    private ISymbolicExpressionTreeNode ParseTerm(Queue<Token> tokens) {
353      var factors = new List<ISymbolicExpressionTreeNode>();
354      var firstFactor = ParseFact(tokens);
355      factors.Add(firstFactor);
356
357      var next = tokens.Peek();
358      while (next.strVal == "*" || next.strVal == "/") {
359        switch (next.strVal) {
360          case "*": {
361              tokens.Dequeue();
362              var fact = ParseFact(tokens);
363              factors.Add(fact);
364              break;
365            }
366          case "/": {
367              tokens.Dequeue();
368              var invFact = ParseFact(tokens);
369              var divNode = GetSymbol("/").CreateTreeNode(); // 1/x
370              divNode.AddSubtree(invFact);
371              factors.Add(divNode);
372              break;
373            }
374        }
375
376        next = tokens.Peek();
377      }
378      if (factors.Count == 1) return factors.First();
379      else {
380        var prod = GetSymbol("*").CreateTreeNode();
381        foreach (var f in factors) prod.AddSubtree(f);
382        return prod;
383      }
384    }
385
386    // Fact = SimpleFact ['^' SimpleFact]
387    private ISymbolicExpressionTreeNode ParseFact(Queue<Token> tokens) {
388      var expr = ParseSimpleFact(tokens);
389      var next = tokens.Peek();
390      if (next.TokenType == TokenType.Operator && next.strVal == "^") {
391        tokens.Dequeue(); // skip;
392
393        var p = GetSymbol("^").CreateTreeNode();
394        p.AddSubtree(expr);
395        p.AddSubtree(ParseSimpleFact(tokens));
396        expr = p;
397      }
398      return expr;
399    }
400
401
402    /// SimpleFact   = '(' Expr ')'
403    ///                 | '{' Expr '}'
404    ///                 | 'LAG' '(' varId ',' ['+' | '-' ] number ')'
405    ///                 | funcId '(' ArgList ')
406    ///                 | VarExpr
407    ///                 | number
408    /// ArgList       = Expr { ',' Expr }
409    /// VarExpr       = varId ( OptFactorPart | OptVectorPart )
410    /// OptFactorPart = [ ('=' varVal | '[' ['+' | '-' ] number {',' ['+' | '-' ] number } ']' ) ]
411    /// OptVectorPart = [ '{' [ number {',' number } ] '}' ]
412    /// varId         =  ident | ' ident ' | " ident "
413    /// varVal        =  ident | ' ident ' | " ident "
414    /// ident         =  '_' | letter { '_' | letter | digit }
415    private ISymbolicExpressionTreeNode ParseSimpleFact(Queue<Token> tokens) {
416      var next = tokens.Peek();
417      if (next.TokenType == TokenType.LeftPar) {
418        var initPar = tokens.Dequeue(); // match par type
419        var expr = ParseExpr(tokens);
420        var rPar = tokens.Dequeue();
421        if (rPar.TokenType != TokenType.RightPar)
422          throw new ArgumentException("expected closing parenthesis");
423        if (initPar.strVal == "(" && rPar.strVal == "}")
424          throw new ArgumentException("expected closing )");
425        if (initPar.strVal == "{" && rPar.strVal == ")")
426          throw new ArgumentException("expected closing }");
427        return expr;
428      } else if (next.TokenType == TokenType.Identifier) {
429        var idTok = tokens.Dequeue();
430        if (tokens.Peek().TokenType == TokenType.LeftPar) {
431          // function identifier or LAG
432          var funcId = idTok.strVal.ToUpperInvariant();
433
434          var funcNode = GetSymbol(funcId).CreateTreeNode();
435          var lPar = tokens.Dequeue();
436          if (lPar.TokenType != TokenType.LeftPar)
437            throw new ArgumentException("expected (");
438
439          // handle 'lag' specifically
440          if (funcNode.Symbol is LaggedVariable) {
441            var varId = tokens.Dequeue();
442            if (varId.TokenType != TokenType.Identifier) throw new ArgumentException("Identifier expected. Format for lagged variables: \"lag(x, -1)\"");
443            var comma = tokens.Dequeue();
444            if (comma.TokenType != TokenType.Comma) throw new ArgumentException("',' expected, Format for lagged variables: \"lag(x, -1)\"");
445            double sign = 1.0;
446            if (tokens.Peek().strVal == "+" || tokens.Peek().strVal == "-") {
447              // read sign
448              var signTok = tokens.Dequeue();
449              if (signTok.strVal == "-") sign = -1.0;
450            }
451            var lagToken = tokens.Dequeue();
452            if (lagToken.TokenType != TokenType.Number) throw new ArgumentException("Number expected, Format for lagged variables: \"lag(x, -1)\"");
453            if (!lagToken.doubleVal.IsAlmost(Math.Round(lagToken.doubleVal)))
454              throw new ArgumentException("Time lags must be integer values");
455            var laggedVarNode = funcNode as LaggedVariableTreeNode;
456            laggedVarNode.VariableName = varId.strVal;
457            laggedVarNode.Lag = (int)Math.Round(sign * lagToken.doubleVal);
458            laggedVarNode.Weight = 1.0;
459          } else {
460            // functions
461            var args = ParseArgList(tokens);
462            // check number of arguments
463            if (funcNode.Symbol.MinimumArity > args.Length || funcNode.Symbol.MaximumArity < args.Length) {
464              throw new ArgumentException(string.Format("Symbol {0} requires between {1} and  {2} arguments.", funcId,
465                funcNode.Symbol.MinimumArity, funcNode.Symbol.MaximumArity));
466            }
467            foreach (var arg in args) funcNode.AddSubtree(arg);
468          }
469
470          var rPar = tokens.Dequeue();
471          if (rPar.TokenType != TokenType.RightPar)
472            throw new ArgumentException("expected )");
473
474
475          return funcNode;
476        } else {
477          // variable
478          if (tokens.Peek().TokenType == TokenType.Eq) {
479            // binary factor
480            tokens.Dequeue(); // skip Eq
481            var valTok = tokens.Dequeue();
482            if (valTok.TokenType != TokenType.Identifier) throw new ArgumentException("expected identifier");
483            var binFactorNode = (BinaryFactorVariableTreeNode)binaryFactorVar.CreateTreeNode();
484            binFactorNode.Weight = 1.0;
485            binFactorNode.VariableName = idTok.strVal;
486            binFactorNode.VariableValue = valTok.strVal;
487            return binFactorNode;
488          } else if (tokens.Peek().TokenType == TokenType.LeftBracket) {
489            // factor variable
490            var factorVariableNode = (FactorVariableTreeNode)factorVar.CreateTreeNode();
491            factorVariableNode.VariableName = idTok.strVal;
492
493            tokens.Dequeue(); // skip [
494            var weights = new List<double>();
495            // at least one weight is necessary
496            var sign = 1.0;
497            if (tokens.Peek().TokenType == TokenType.Operator) {
498              var opToken = tokens.Dequeue();
499              if (opToken.strVal == "+") sign = 1.0;
500              else if (opToken.strVal == "-") sign = -1.0;
501              else throw new ArgumentException();
502            }
503            if (tokens.Peek().TokenType != TokenType.Number) throw new ArgumentException("number expected");
504            var weightTok = tokens.Dequeue();
505            weights.Add(sign * weightTok.doubleVal);
506            while (tokens.Peek().TokenType == TokenType.Comma) {
507              // skip comma
508              tokens.Dequeue();
509              if (tokens.Peek().TokenType == TokenType.Operator) {
510                var opToken = tokens.Dequeue();
511                if (opToken.strVal == "+") sign = 1.0;
512                else if (opToken.strVal == "-") sign = -1.0;
513                else throw new ArgumentException();
514              }
515              weightTok = tokens.Dequeue();
516              if (weightTok.TokenType != TokenType.Number) throw new ArgumentException("number expected");
517              weights.Add(sign * weightTok.doubleVal);
518            }
519            var rightBracketToken = tokens.Dequeue();
520            if (rightBracketToken.TokenType != TokenType.RightBracket) throw new ArgumentException("closing bracket ] expected");
521            factorVariableNode.Weights = weights.ToArray();
522            return factorVariableNode;
523          } else if (tokens.Peek().TokenType == TokenType.LeftBrace) {
524            // vector variable
525            var vecVarNode = (VariableTreeNodeBase)vectorVariable.CreateTreeNode();
526            vecVarNode.VariableName = idTok.strVal;
527            vecVarNode.Weight = 1.0;
528            tokens.Dequeue(); // skip {
529            // optional vector length (currently unused)
530            var lengths = new List<int>();
531            if (tokens.Peek().TokenType == TokenType.Number) {
532              var lengthToken = tokens.Dequeue();
533              lengths.Add((int)lengthToken.doubleVal);
534              while (tokens.Peek().TokenType == TokenType.Comma) {
535                tokens.Dequeue(); // skip comma
536                lengthToken = tokens.Dequeue();
537                if (lengthToken.TokenType != TokenType.Number) throw new ArgumentException("number expected");
538                lengths.Add((int)lengthToken.doubleVal);
539              }
540            }
541            var rightBraceToken = tokens.Dequeue();
542            if (rightBraceToken.TokenType != TokenType.RightBrace) throw new ArgumentException("closing brace } expected");
543            return vecVarNode;
544          } else {
545            // variable
546            var varNode = (VariableTreeNode)variable.CreateTreeNode();
547            varNode.Weight = 1.0;
548            varNode.VariableName = idTok.strVal;
549            return varNode;
550          }
551        }
552      } else if (next.TokenType == TokenType.Number) {
553        var numTok = tokens.Dequeue();
554        var constNode = (ConstantTreeNode)constant.CreateTreeNode();
555        constNode.Value = numTok.doubleVal;
556        return constNode;
557      } else {
558        throw new ArgumentException(string.Format("unexpected token in expression {0}", next.strVal));
559      }
560    }
561
562    // ArgList = Expr { ',' Expr }
563    private ISymbolicExpressionTreeNode[] ParseArgList(Queue<Token> tokens) {
564      var exprList = new List<ISymbolicExpressionTreeNode>();
565      exprList.Add(ParseExpr(tokens));
566      while (tokens.Peek().TokenType != TokenType.RightPar) {
567        var comma = tokens.Dequeue();
568        if (comma.TokenType != TokenType.Comma) throw new ArgumentException("expected ',' ");
569        exprList.Add(ParseExpr(tokens));
570      }
571      return exprList.ToArray();
572    }
573  }
574}
Note: See TracBrowser for help on using the repository browser.