Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/HeuristicLab.Problems.DataAnalysis.Symbolic/3.4/Importer/InfixExpressionParser.cs

Last change on this file was 18220, checked in by gkronber, 2 years ago

#3136: reintegrated structure-template GP branch into trunk

File size: 26.4 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
31namespace HeuristicLab.Problems.DataAnalysis.Symbolic {
32  /// <summary>
33  /// Parses mathematical expressions in infix form. E.g. x1 * (3.0 * x2 + x3)
34  /// Identifier format (functions or variables): '_' | letter { '_' | letter | digit }
35  /// Variables names and variable values can be set under quotes "" or '' because variable names might contain spaces.
36  ///   Variable = ident | " ident " | ' ident '
37  /// It is also possible to use functions e.g. log("x1") or real-valued constants e.g. 3.1415 .
38  /// Variable names are case sensitive. Function names are not case sensitive.
39  ///
40  ///
41  /// S             = Expr EOF
42  /// Expr          = Term { '+' Term | '-' Term }
43  /// Term          = Fact { '*' Fact | '/' Fact }
44  /// Fact          = SimpleFact [ '^' SimpleFact ]
45  /// SimpleFact    = '(' Expr ')'
46  ///                 | '{' Expr '}'
47  ///                 | 'LAG' '(' varId ',' ['+' | '-' ] number ')
48  ///                 | funcId '(' ArgList ')'
49  ///                 | VarExpr
50  ///                 | number
51  ///                 | ['+' | '-'] SimpleFact
52  /// ArgList       = Expr { ',' Expr }
53  /// VarExpr       = varId OptFactorPart
54  /// OptFactorPart = [ ('=' varVal | '[' ['+' | '-' ]  number {',' ['+' | '-' ]  number } ']' ) ]
55  /// varId         =  ident | ' ident ' | " ident "
56  /// varVal        =  ident | ' ident ' | " ident "
57  /// ident         =  '_' | letter { '_' | letter | digit }
58  /// </summary>
59  public sealed class InfixExpressionParser {
60    private enum TokenType { Operator, Identifier, Number, LeftPar, RightPar, LeftBracket, RightBracket, LeftAngleBracket, RightAngleBracket, Comma, Eq, End, NA };
61    private class Token {
62      internal double doubleVal;
63      internal string strVal;
64      internal TokenType TokenType;
65    }
66
67    private class SymbolComparer : IEqualityComparer<ISymbol>, IComparer<ISymbol> {
68      public int Compare(ISymbol x, ISymbol y) {
69        return x.Name.CompareTo(y.Name);
70      }
71
72      public bool Equals(ISymbol x, ISymbol y) {
73        return x.GetType() == y.GetType();
74      }
75
76      public int GetHashCode(ISymbol obj) {
77        return obj.GetType().GetHashCode();
78      }
79    }
80    // format name <-> symbol
81    // the lookup table is also used in the corresponding formatter
82    internal static readonly BidirectionalLookup<string, ISymbol>
83      knownSymbols = new BidirectionalLookup<string, ISymbol>(StringComparer.InvariantCulture, new SymbolComparer());
84    internal static readonly SubFunctionSymbol subFunctionSymbol = new SubFunctionSymbol();
85
86    private Number number = new Number();
87    private Constant minusOne = new Constant() { Value = -1 };
88    private Variable variable = new Variable();
89    private BinaryFactorVariable binaryFactorVar = new BinaryFactorVariable();
90    private FactorVariable factorVar = new FactorVariable();
91
92    private ProgramRootSymbol programRootSymbol = new ProgramRootSymbol();
93    private StartSymbol startSymbol = new StartSymbol();
94
95    static InfixExpressionParser() {
96      // populate bidirectional lookup
97      var dict = new Dictionary<string, ISymbol>
98      {
99        { "+", new Addition()},
100        { "/", new Division()},
101        { "*", new Multiplication()},
102        { "-", new Subtraction()},
103        { "^", new Power() },
104        { "ABS", new Absolute() },
105        { "EXP", new Exponential()},
106        { "LOG", new Logarithm()},
107        { "POW", new Power() },
108        { "ROOT", new Root()},
109        { "SQR", new Square() },
110        { "SQRT", new SquareRoot() },
111        { "CUBE", new Cube() },
112        { "CUBEROOT", new CubeRoot() },
113        { "SIN",new Sine()},
114        { "COS", new Cosine()},
115        { "TAN", new Tangent()},
116        { "TANH", new HyperbolicTangent()},
117        { "AIRYA", new AiryA()},
118        { "AIRYB", new AiryB()},
119        { "BESSEL", new Bessel()},
120        { "COSINT", new CosineIntegral()},
121        { "SININT", new SineIntegral()},
122        { "HYPCOSINT", new HyperbolicCosineIntegral()},
123        { "HYPSININT", new HyperbolicSineIntegral()},
124        { "FRESNELSININT", new FresnelSineIntegral()},
125        { "FRESNELCOSINT", new FresnelCosineIntegral()},
126        { "NORM", new Norm()},
127        { "ERF", new Erf()},
128        { "GAMMA", new Gamma()},
129        { "PSI", new Psi()},
130        { "DAWSON", new Dawson()},
131        { "EXPINT", new ExponentialIntegralEi()},
132        { "AQ", new AnalyticQuotient() },
133        { "MEAN", new Average()},
134        { "IF", new IfThenElse()},
135        { "GT", new GreaterThan()},
136        { "LT", new LessThan()},
137        { "AND", new And()},
138        { "OR", new Or()},
139        { "NOT", new Not()},
140        { "XOR", new Xor()},
141        { "DIFF", new Derivative()},
142        { "LAG", new LaggedVariable() },
143      };
144
145
146      foreach (var kvp in dict) {
147        knownSymbols.Add(kvp.Key, kvp.Value);
148      }
149    }
150
151    public ISymbolicExpressionTree Parse(string str) {
152      ISymbolicExpressionTreeNode root = programRootSymbol.CreateTreeNode();
153      ISymbolicExpressionTreeNode start = startSymbol.CreateTreeNode();
154      var allTokens = GetAllTokens(str).ToArray();
155      ISymbolicExpressionTreeNode mainBranch = ParseS(new Queue<Token>(allTokens));
156
157      // only a main branch was given => insert the main branch into the default tree template
158      root.AddSubtree(start);
159      start.AddSubtree(mainBranch);
160      return new SymbolicExpressionTree(root);
161    }
162
163    private IEnumerable<Token> GetAllTokens(string str) {
164      int pos = 0;
165      while (true) {
166        while (pos < str.Length && char.IsWhiteSpace(str[pos])) pos++;
167        if (pos >= str.Length) {
168          yield return new Token { TokenType = TokenType.End, strVal = "" };
169          yield break;
170        }
171        if (char.IsDigit(str[pos])) {
172          // read number (=> read until white space or other symbol)
173          var sb = new StringBuilder();
174          sb.Append(str[pos]);
175          pos++;
176          while (pos < str.Length && !char.IsWhiteSpace(str[pos])
177            && (str[pos] != '+' || str[pos - 1] == 'e' || str[pos - 1] == 'E')     // continue reading exponents
178            && (str[pos] != '-' || str[pos - 1] == 'e' || str[pos - 1] == 'E')
179            && str[pos] != '*'
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.LeftPar, strVal = "{" };
262        } else if (str[pos] == '}') {
263          pos++;
264          yield return new Token { TokenType = TokenType.RightPar, 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 if (str[pos] == '<') {
272          pos++;
273          yield return new Token { TokenType = TokenType.LeftAngleBracket, strVal = "<" };
274        } else if (str[pos] == '>') {
275          pos++;
276          yield return new Token { TokenType = TokenType.RightAngleBracket, strVal = ">" };
277        } else {
278          throw new ArgumentException("Invalid character: " + str[pos]);
279        }
280      }
281    }
282    /// S             = Expr EOF
283    private ISymbolicExpressionTreeNode ParseS(Queue<Token> tokens) {
284      var expr = ParseExpr(tokens);
285
286      var endTok = tokens.Dequeue();
287      if (endTok.TokenType != TokenType.End)
288        throw new ArgumentException(string.Format("Expected end of expression (got {0})", endTok.strVal));
289
290      return expr;
291    }
292
293    /// Expr          = Term { '+' Term | '-' Term }
294    private ISymbolicExpressionTreeNode ParseExpr(Queue<Token> tokens) {
295      // build tree from bottom to top and left to right
296      // a + b - c => ((a + b) - c)
297      // a - b - c => ((a - b) - c)
298      // and then flatten as far as possible
299      var left = ParseTerm(tokens);
300
301      var next = tokens.Peek();
302      while (next.strVal == "+" || next.strVal == "-") {
303        switch (next.strVal) {
304          case "+": {
305              tokens.Dequeue();
306              var right = ParseTerm(tokens);
307              var op = GetSymbol("+").CreateTreeNode();
308              op.AddSubtree(left);
309              op.AddSubtree(right);
310              left = op;
311              break;
312            }
313          case "-": {
314              tokens.Dequeue();
315              var right = ParseTerm(tokens);
316              var op = GetSymbol("-").CreateTreeNode();
317              op.AddSubtree(left);
318              op.AddSubtree(right);
319              left = op;
320              break;
321            }
322        }
323        next = tokens.Peek();
324      }
325
326      FoldLeftRecursive(left);
327      return left;
328    }
329
330    private ISymbol GetSymbol(string tok) {
331      if (knownSymbols.ContainsFirst(tok))
332        return knownSymbols.GetByFirst(tok).FirstOrDefault();
333      else
334        return subFunctionSymbol;
335    }
336
337    /// Term          = Fact { '*' Fact | '/' Fact }
338    private ISymbolicExpressionTreeNode ParseTerm(Queue<Token> tokens) {
339      // build tree from bottom to top and left to right
340      // a / b * c => ((a / b) * c)
341      // a / b / c => ((a / b) / c)
342      // and then flatten as far as possible
343
344      var left = ParseFact(tokens);
345
346      var next = tokens.Peek();
347      while (next.strVal == "*" || next.strVal == "/") {
348        switch (next.strVal) {
349          case "*": {
350              tokens.Dequeue();
351              var right = ParseFact(tokens);
352
353              var op = GetSymbol("*").CreateTreeNode();
354              op.AddSubtree(left);
355              op.AddSubtree(right);
356              left = op;
357              break;
358            }
359          case "/": {
360              tokens.Dequeue();
361              var right = ParseFact(tokens);
362              var op = GetSymbol("/").CreateTreeNode();
363              op.AddSubtree(left);
364              op.AddSubtree(right);
365              left = op;
366              break;
367            }
368        }
369
370        next = tokens.Peek();
371      }
372      // remove all nodes where the child op is the same as the parent op
373      // (a * b) * c) => (a * b * c)
374      // (a / b) / c) => (a / b / c)
375
376      FoldLeftRecursive(left);
377      return left;
378    }
379
380    private void FoldLeftRecursive(ISymbolicExpressionTreeNode parent) {
381      if (parent.SubtreeCount > 1) {
382        var child = parent.GetSubtree(0);
383        FoldLeftRecursive(child);
384        if (parent.Symbol == child.Symbol && IsAssociative(parent.Symbol)) {
385          parent.RemoveSubtree(0);
386          for (int i = 0; i < child.SubtreeCount; i++) {
387            parent.InsertSubtree(i, child.GetSubtree(i));
388          }
389        }
390      }
391    }
392
393    // Fact = SimpleFact ['^' SimpleFact]
394    private ISymbolicExpressionTreeNode ParseFact(Queue<Token> tokens) {
395      var expr = ParseSimpleFact(tokens);
396      var next = tokens.Peek();
397      if (next.TokenType == TokenType.Operator && next.strVal == "^") {
398        tokens.Dequeue(); // skip;
399
400        var p = GetSymbol("^").CreateTreeNode();
401        p.AddSubtree(expr);
402        p.AddSubtree(ParseSimpleFact(tokens));
403        expr = p;
404      }
405      return expr;
406    }
407
408
409    /// SimpleFact   = '(' Expr ')'
410    ///                 | '{' Expr '}'
411    ///                 | 'LAG' '(' varId ',' ['+' | '-' ] number ')'
412    ///                 | funcId '(' ArgList ')
413    ///                 | VarExpr
414    ///                 | '<' 'num' [ '=' [ '+' | '-' ] number ] '>'
415    ///                 | number
416    ///                 | ['+' | '-' ] SimpleFact
417    /// ArgList       = Expr { ',' Expr }
418    /// VarExpr       = varId OptFactorPart
419    /// OptFactorPart = [ ('=' varVal | '[' ['+' | '-' ] number {',' ['+' | '-' ] number } ']' ) ]
420    /// varId         =  ident | ' ident ' | " ident "
421    /// varVal        =  ident | ' ident ' | " ident "
422    /// ident         =  '_' | letter { '_' | letter | digit }
423    private ISymbolicExpressionTreeNode ParseSimpleFact(Queue<Token> tokens) {
424      var next = tokens.Peek();
425      if (next.TokenType == TokenType.LeftPar) {
426        var initPar = tokens.Dequeue(); // match par type
427        var expr = ParseExpr(tokens);
428        var rPar = tokens.Dequeue();
429        if (rPar.TokenType != TokenType.RightPar)
430          throw new ArgumentException("expected closing parenthesis");
431        if (initPar.strVal == "(" && rPar.strVal == "}")
432          throw new ArgumentException("expected closing )");
433        if (initPar.strVal == "{" && rPar.strVal == ")")
434          throw new ArgumentException("expected closing }");
435        return expr;
436      } else if (next.TokenType == TokenType.Identifier) {
437        var idTok = tokens.Dequeue();
438        if (tokens.Peek().TokenType == TokenType.LeftPar) {
439          // function identifier or LAG
440          return ParseFunctionOrLaggedVariable(tokens, idTok);
441        } else {
442          return ParseVariable(tokens, idTok);
443        }
444      } else if (next.TokenType == TokenType.LeftAngleBracket) {
445        // '<' 'num' [ '=' ['+'|'-'] number ] '>'
446        return ParseNumber(tokens);
447      } else if (next.TokenType == TokenType.Operator && (next.strVal == "-" || next.strVal == "+")) {
448        // ['+' | '-' ] SimpleFact
449        if (tokens.Dequeue().strVal == "-") {
450          var arg = ParseSimpleFact(tokens);
451          if (arg is NumberTreeNode numNode) {
452            numNode.Value *= -1;
453            return numNode;
454          } else if (arg is ConstantTreeNode constNode) {
455            var constSy = new Constant { Value = -constNode.Value };
456            return constSy.CreateTreeNode();
457          } else if (arg is VariableTreeNode varNode) {
458            varNode.Weight *= -1;
459            return varNode;
460          } else {
461            var mul = GetSymbol("*").CreateTreeNode();
462            var neg = minusOne.CreateTreeNode();
463            mul.AddSubtree(neg);
464            mul.AddSubtree(arg);
465            return mul;
466          }
467        } else {
468          return ParseSimpleFact(tokens);
469        }
470      } else if (next.TokenType == TokenType.Number) {
471        // number
472        var numTok = tokens.Dequeue();
473        var constSy = new Constant { Value = numTok.doubleVal };
474        return constSy.CreateTreeNode();
475      } else {
476        throw new ArgumentException(string.Format("unexpected token in expression {0}", next.strVal));
477      }
478    }
479
480    private ISymbolicExpressionTreeNode ParseNumber(Queue<Token> tokens) {
481      // we distinguish parameters and constants. The values of parameters can be changed.
482      // a parameter is written as '<' 'num' [ '=' ['+'|'-'] number ] '>' with an optional initialization
483      Token numberTok = null;
484      var leftAngleBracket = tokens.Dequeue();
485      if (leftAngleBracket.TokenType != TokenType.LeftAngleBracket)
486        throw new ArgumentException("opening bracket < expected");
487
488      var idTok = tokens.Dequeue();
489      if (idTok.TokenType != TokenType.Identifier || idTok.strVal.ToLower() != "num")
490        throw new ArgumentException("string 'num' expected");
491
492      var numNode = (NumberTreeNode)number.CreateTreeNode();
493
494      if (tokens.Peek().TokenType == TokenType.Eq) {
495        tokens.Dequeue(); // skip "="
496        var next = tokens.Peek();
497        if (next.strVal != "+" && next.strVal != "-" && next.TokenType != TokenType.Number)
498          throw new ArgumentException("Expected '+', '-' or number.");
499
500        var sign = 1.0;
501        if (next.strVal == "+" || next.strVal == "-") {
502          if (tokens.Dequeue().strVal == "-") sign = -1.0;
503        }
504        if (tokens.Peek().TokenType != TokenType.Number) {
505          throw new ArgumentException("Expected number.");
506        }
507        numberTok = tokens.Dequeue();
508        numNode.Value = sign * numberTok.doubleVal;
509      }
510
511      var rightAngleBracket = tokens.Dequeue();
512      if (rightAngleBracket.TokenType != TokenType.RightAngleBracket)
513        throw new ArgumentException("closing bracket > expected");
514
515      return numNode;
516    }
517
518    private ISymbolicExpressionTreeNode ParseVariable(Queue<Token> tokens, Token idTok) {
519      // variable
520      if (tokens.Peek().TokenType == TokenType.Eq) {
521        // binary factor
522        tokens.Dequeue(); // skip Eq
523        var valTok = tokens.Dequeue();
524        if (valTok.TokenType != TokenType.Identifier) throw new ArgumentException("expected identifier");
525        var binFactorNode = (BinaryFactorVariableTreeNode)binaryFactorVar.CreateTreeNode();
526        binFactorNode.Weight = 1.0;
527        binFactorNode.VariableName = idTok.strVal;
528        binFactorNode.VariableValue = valTok.strVal;
529        return binFactorNode;
530      } else if (tokens.Peek().TokenType == TokenType.LeftBracket) {
531        // factor variable
532        var factorVariableNode = (FactorVariableTreeNode)factorVar.CreateTreeNode();
533        factorVariableNode.VariableName = idTok.strVal;
534
535        tokens.Dequeue(); // skip [
536        var weights = new List<double>();
537        // at least one weight is necessary
538        var sign = 1.0;
539        if (tokens.Peek().TokenType == TokenType.Operator) {
540          var opToken = tokens.Dequeue();
541          if (opToken.strVal == "+") sign = 1.0;
542          else if (opToken.strVal == "-") sign = -1.0;
543          else throw new ArgumentException();
544        }
545        if (tokens.Peek().TokenType != TokenType.Number) throw new ArgumentException("number expected");
546        var weightTok = tokens.Dequeue();
547        weights.Add(sign * weightTok.doubleVal);
548        while (tokens.Peek().TokenType == TokenType.Comma) {
549          // skip comma
550          tokens.Dequeue();
551          if (tokens.Peek().TokenType == TokenType.Operator) {
552            var opToken = tokens.Dequeue();
553            if (opToken.strVal == "+") sign = 1.0;
554            else if (opToken.strVal == "-") sign = -1.0;
555            else throw new ArgumentException();
556          }
557          weightTok = tokens.Dequeue();
558          if (weightTok.TokenType != TokenType.Number) throw new ArgumentException("number expected");
559          weights.Add(sign * weightTok.doubleVal);
560        }
561        var rightBracketToken = tokens.Dequeue();
562        if (rightBracketToken.TokenType != TokenType.RightBracket) throw new ArgumentException("closing bracket ] expected");
563        factorVariableNode.Weights = weights.ToArray();
564        return factorVariableNode;
565      } else {
566        // variable
567        var varNode = (VariableTreeNode)variable.CreateTreeNode();
568        varNode.Weight = 1.0;
569        varNode.VariableName = idTok.strVal;
570        return varNode;
571      }
572    }
573
574    private ISymbolicExpressionTreeNode ParseFunctionOrLaggedVariable(Queue<Token> tokens, Token idTok) {
575      var funcId = idTok.strVal.ToUpperInvariant();
576
577      var funcNode = GetSymbol(funcId).CreateTreeNode();
578      var lPar = tokens.Dequeue();
579      if (lPar.TokenType != TokenType.LeftPar)
580        throw new ArgumentException("expected (");
581
582      // handle 'lag' specifically
583      if (funcNode.Symbol is LaggedVariable) {
584        ParseLaggedVariable(tokens, funcNode);
585      } else if (funcNode.Symbol is SubFunctionSymbol) { // SubFunction
586        var subFunction = funcNode as SubFunctionTreeNode;
587        subFunction.Name = idTok.strVal;
588        // input arguments
589        var args = ParseArgList(tokens);
590        IList<string> arguments = new List<string>();
591        foreach (var arg in args)
592          if (arg is VariableTreeNode varTreeNode)
593            arguments.Add(varTreeNode.VariableName);
594        subFunction.Arguments = arguments;
595      } else {
596        // functions
597        var args = ParseArgList(tokens);
598        // check number of arguments
599        if (funcNode.Symbol.MinimumArity > args.Length || funcNode.Symbol.MaximumArity < args.Length) {
600          throw new ArgumentException(string.Format("Symbol {0} requires between {1} and  {2} arguments.", funcId,
601            funcNode.Symbol.MinimumArity, funcNode.Symbol.MaximumArity));
602        }
603        foreach (var arg in args) funcNode.AddSubtree(arg);
604      }
605
606      var rPar = tokens.Dequeue();
607      if (rPar.TokenType != TokenType.RightPar)
608        throw new ArgumentException("expected )");
609
610
611      return funcNode;
612    }
613
614    private static void ParseLaggedVariable(Queue<Token> tokens, ISymbolicExpressionTreeNode funcNode) {
615      var varId = tokens.Dequeue();
616      if (varId.TokenType != TokenType.Identifier) throw new ArgumentException("Identifier expected. Format for lagged variables: \"lag(x, -1)\"");
617      var comma = tokens.Dequeue();
618      if (comma.TokenType != TokenType.Comma) throw new ArgumentException("',' expected, Format for lagged variables: \"lag(x, -1)\"");
619      double sign = 1.0;
620      if (tokens.Peek().strVal == "+" || tokens.Peek().strVal == "-") {
621        // read sign
622        var signTok = tokens.Dequeue();
623        if (signTok.strVal == "-") sign = -1.0;
624      }
625      var lagToken = tokens.Dequeue();
626      if (lagToken.TokenType != TokenType.Number) throw new ArgumentException("Number expected, Format for lagged variables: \"lag(x, -1)\"");
627      if (!lagToken.doubleVal.IsAlmost(Math.Round(lagToken.doubleVal)))
628        throw new ArgumentException("Time lags must be integer values");
629      var laggedVarNode = funcNode as LaggedVariableTreeNode;
630      laggedVarNode.VariableName = varId.strVal;
631      laggedVarNode.Lag = (int)Math.Round(sign * lagToken.doubleVal);
632      laggedVarNode.Weight = 1.0;
633    }
634
635    // ArgList = Expr { ',' Expr }
636    private ISymbolicExpressionTreeNode[] ParseArgList(Queue<Token> tokens) {
637      var exprList = new List<ISymbolicExpressionTreeNode>();
638      exprList.Add(ParseExpr(tokens));
639      while (tokens.Peek().TokenType != TokenType.RightPar) {
640        var comma = tokens.Dequeue();
641        if (comma.TokenType != TokenType.Comma) throw new ArgumentException("expected ',' ");
642        exprList.Add(ParseExpr(tokens));
643      }
644      return exprList.ToArray();
645    }
646
647    private bool IsAssociative(ISymbol sy) {
648      return sy == GetSymbol("+") || sy == GetSymbol("-") ||
649             sy == GetSymbol("*") || sy == GetSymbol("/") ||
650             sy == GetSymbol("AND") || sy == GetSymbol("OR") || sy == GetSymbol("XOR");
651    }
652  }
653}
Note: See TracBrowser for help on using the repository browser.