#region License Information /* HeuristicLab * Copyright (C) 2002-2018 Heuristic and Evolutionary Algorithms Laboratory (HEAL) * * This file is part of HeuristicLab. * * HeuristicLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * HeuristicLab is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with HeuristicLab. If not, see . */ #endregion using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using HeuristicLab.Collections; using HeuristicLab.Common; using HeuristicLab.Encodings.SymbolicExpressionTreeEncoding; namespace HeuristicLab.Problems.DataAnalysis.Symbolic { /// /// Parses mathematical expressions in infix form. E.g. x1 * (3.0 * x2 + x3) /// Identifier format (functions or variables): '_' | letter { '_' | letter | digit } /// Variables names and variable values can be set under quotes "" or '' because variable names might contain spaces. /// Variable = ident | " ident " | ' ident ' /// It is also possible to use functions e.g. log("x1") or real-valued constants e.g. 3.1415 . /// Variable names are case sensitive. Function names are not case sensitive. /// /// /// S = Expr EOF /// Expr = ['-' | '+'] Term { '+' Term | '-' Term } /// Term = Fact { '*' Fact | '/' Fact } /// Fact = SimpleFact [ '^' SimpleFact ] /// SimpleFact = '(' Expr ')' /// | '{' Expr '}' /// | 'LAG' '(' varId ',' ['+' | '-' ] number ') /// | funcId '(' ArgList ')' /// | VarExpr /// | number /// ArgList = Expr { ',' Expr } /// VarExpr = varId OptFactorPart /// OptFactorPart = [ ('=' varVal | '[' ['+' | '-' ] number {',' ['+' | '-' ] number } ']' ) ] /// varId = ident | ' ident ' | " ident " /// varVal = ident | ' ident ' | " ident " /// ident = '_' | letter { '_' | letter | digit } /// public sealed class InfixExpressionParser { private enum TokenType { Operator, Identifier, Number, LeftPar, RightPar, LeftBracket, RightBracket, Comma, Eq, End, NA }; private class Token { internal double doubleVal; internal string strVal; internal TokenType TokenType; } private class SymbolNameComparer : IEqualityComparer, IComparer { public int Compare(ISymbol x, ISymbol y) { return x.Name.CompareTo(y.Name); } public bool Equals(ISymbol x, ISymbol y) { return Compare(x, y) == 0; } public int GetHashCode(ISymbol obj) { return obj.Name.GetHashCode(); } } // format name <-> symbol // the lookup table is also used in the corresponding formatter internal static readonly BidirectionalLookup knownSymbols = new BidirectionalLookup(StringComparer.InvariantCulture, new SymbolNameComparer()); private Constant constant = new Constant(); private Variable variable = new Variable(); private BinaryFactorVariable binaryFactorVar = new BinaryFactorVariable(); private FactorVariable factorVar = new FactorVariable(); private ProgramRootSymbol programRootSymbol = new ProgramRootSymbol(); private StartSymbol startSymbol = new StartSymbol(); static InfixExpressionParser() { // populate bidirectional lookup var dict = new Dictionary { { "+", new Addition()}, { "/", new Division()}, { "*", new Multiplication()}, { "-", new Subtraction()}, { "^", new Power() }, { "ABS", new Absolute() }, { "EXP", new Exponential()}, { "LOG", new Logarithm()}, { "POW", new Power() }, { "ROOT", new Root()}, { "SQR", new Square() }, { "SQRT", new SquareRoot() }, { "CUBE", new Cube() }, { "CUBEROOT", new CubeRoot() }, { "SIN",new Sine()}, { "COS", new Cosine()}, { "TAN", new Tangent()}, { "AIRYA", new AiryA()}, { "AIRYB", new AiryB()}, { "BESSEL", new Bessel()}, { "COSINT", new CosineIntegral()}, { "SININT", new SineIntegral()}, { "HYPCOSINT", new HyperbolicCosineIntegral()}, { "HYPSININT", new HyperbolicSineIntegral()}, { "FRESNELSININT", new FresnelSineIntegral()}, { "FRESNELCOSINT", new FresnelCosineIntegral()}, { "NORM", new Norm()}, { "ERF", new Erf()}, { "GAMMA", new Gamma()}, { "PSI", new Psi()}, { "DAWSON", new Dawson()}, { "EXPINT", new ExponentialIntegralEi()}, { "AQ", new AnalyticQuotient() }, { "MEAN", new Average()}, { "IF", new IfThenElse()}, { "GT", new GreaterThan()}, { "LT", new LessThan()}, { "AND", new And()}, { "OR", new Or()}, { "NOT", new Not()}, { "XOR", new Xor()}, { "DIFF", new Derivative()}, { "LAG", new LaggedVariable() }, }; foreach (var kvp in dict) { knownSymbols.Add(kvp.Key, kvp.Value); } } public ISymbolicExpressionTree Parse(string str) { ISymbolicExpressionTreeNode root = programRootSymbol.CreateTreeNode(); ISymbolicExpressionTreeNode start = startSymbol.CreateTreeNode(); var allTokens = GetAllTokens(str).ToArray(); ISymbolicExpressionTreeNode mainBranch = ParseS(new Queue(allTokens)); // only a main branch was given => insert the main branch into the default tree template root.AddSubtree(start); start.AddSubtree(mainBranch); return new SymbolicExpressionTree(root); } private IEnumerable GetAllTokens(string str) { int pos = 0; while (true) { while (pos < str.Length && Char.IsWhiteSpace(str[pos])) pos++; if (pos >= str.Length) { yield return new Token { TokenType = TokenType.End, strVal = "" }; yield break; } if (char.IsDigit(str[pos])) { // read number (=> read until white space or operator or comma) var sb = new StringBuilder(); sb.Append(str[pos]); pos++; while (pos < str.Length && !char.IsWhiteSpace(str[pos]) && (str[pos] != '+' || str[pos - 1] == 'e' || str[pos - 1] == 'E') // continue reading exponents && (str[pos] != '-' || str[pos - 1] == 'e' || str[pos - 1] == 'E') && str[pos] != '*' && str[pos] != '/' && str[pos] != '^' && str[pos] != ')' && str[pos] != ']' && str[pos] != '}' && str[pos] != ',') { sb.Append(str[pos]); pos++; } double dblVal; if (double.TryParse(sb.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture, out dblVal)) yield return new Token { TokenType = TokenType.Number, strVal = sb.ToString(), doubleVal = dblVal }; else yield return new Token { TokenType = TokenType.NA, strVal = sb.ToString() }; } else if (char.IsLetter(str[pos]) || str[pos] == '_') { // read ident var sb = new StringBuilder(); sb.Append(str[pos]); pos++; while (pos < str.Length && (char.IsLetter(str[pos]) || str[pos] == '_' || char.IsDigit(str[pos]))) { sb.Append(str[pos]); pos++; } yield return new Token { TokenType = TokenType.Identifier, strVal = sb.ToString() }; } else if (str[pos] == '"') { // read to next " pos++; var sb = new StringBuilder(); while (pos < str.Length && str[pos] != '"') { sb.Append(str[pos]); pos++; } if (pos < str.Length && str[pos] == '"') { pos++; // skip " yield return new Token { TokenType = TokenType.Identifier, strVal = sb.ToString() }; } else yield return new Token { TokenType = TokenType.NA }; } else if (str[pos] == '\'') { // read to next ' pos++; var sb = new StringBuilder(); while (pos < str.Length && str[pos] != '\'') { sb.Append(str[pos]); pos++; } if (pos < str.Length && str[pos] == '\'') { pos++; // skip ' yield return new Token { TokenType = TokenType.Identifier, strVal = sb.ToString() }; } else yield return new Token { TokenType = TokenType.NA }; } else if (str[pos] == '+') { pos++; yield return new Token { TokenType = TokenType.Operator, strVal = "+" }; } else if (str[pos] == '-') { pos++; yield return new Token { TokenType = TokenType.Operator, strVal = "-" }; } else if (str[pos] == '/') { pos++; yield return new Token { TokenType = TokenType.Operator, strVal = "/" }; } else if (str[pos] == '*') { pos++; yield return new Token { TokenType = TokenType.Operator, strVal = "*" }; } else if (str[pos] == '^') { pos++; yield return new Token { TokenType = TokenType.Operator, strVal = "^" }; } else if (str[pos] == '(') { pos++; yield return new Token { TokenType = TokenType.LeftPar, strVal = "(" }; } else if (str[pos] == ')') { pos++; yield return new Token { TokenType = TokenType.RightPar, strVal = ")" }; } else if (str[pos] == '[') { pos++; yield return new Token { TokenType = TokenType.LeftBracket, strVal = "[" }; } else if (str[pos] == ']') { pos++; yield return new Token { TokenType = TokenType.RightBracket, strVal = "]" }; } else if (str[pos] == '{') { pos++; yield return new Token { TokenType = TokenType.LeftPar, strVal = "{" }; } else if (str[pos] == '}') { pos++; yield return new Token { TokenType = TokenType.RightPar, strVal = "}" }; } else if (str[pos] == '=') { pos++; yield return new Token { TokenType = TokenType.Eq, strVal = "=" }; } else if (str[pos] == ',') { pos++; yield return new Token { TokenType = TokenType.Comma, strVal = "," }; } else { throw new ArgumentException("Invalid character: " + str[pos]); } } } /// S = Expr EOF private ISymbolicExpressionTreeNode ParseS(Queue tokens) { var expr = ParseExpr(tokens); var endTok = tokens.Dequeue(); if (endTok.TokenType != TokenType.End) throw new ArgumentException(string.Format("Expected end of expression (got {0})", endTok.strVal)); return expr; } /// Expr = ['-' | '+'] Term { '+' Term | '-' Term } private ISymbolicExpressionTreeNode ParseExpr(Queue tokens) { var next = tokens.Peek(); var posTerms = new List(); var negTerms = new List(); bool negateFirstTerm = false; if (next.TokenType == TokenType.Operator && (next.strVal == "+" || next.strVal == "-")) { tokens.Dequeue(); if (next.strVal == "-") negateFirstTerm = true; } var t = ParseTerm(tokens); if (negateFirstTerm) negTerms.Add(t); else posTerms.Add(t); next = tokens.Peek(); while (next.strVal == "+" || next.strVal == "-") { switch (next.strVal) { case "+": { tokens.Dequeue(); var term = ParseTerm(tokens); posTerms.Add(term); break; } case "-": { tokens.Dequeue(); var term = ParseTerm(tokens); negTerms.Add(term); break; } } next = tokens.Peek(); } var sum = GetSymbol("+").CreateTreeNode(); foreach (var posTerm in posTerms) sum.AddSubtree(posTerm); if (negTerms.Any()) { if (negTerms.Count == 1) { var sub = GetSymbol("-").CreateTreeNode(); sub.AddSubtree(negTerms.Single()); sum.AddSubtree(sub); } else { var sumNeg = GetSymbol("+").CreateTreeNode(); foreach (var negTerm in negTerms) sumNeg.AddSubtree(negTerm); var constNode = (ConstantTreeNode)constant.CreateTreeNode(); constNode.Value = -1.0; var prod = GetSymbol("*").CreateTreeNode(); prod.AddSubtree(constNode); prod.AddSubtree(sumNeg); sum.AddSubtree(prod); } } if (sum.SubtreeCount == 1) return sum.Subtrees.First(); else return sum; } private ISymbol GetSymbol(string tok) { var symb = knownSymbols.GetByFirst(tok).FirstOrDefault(); if (symb == null) throw new ArgumentException(string.Format("Unknown token {0} found.", tok)); return symb; } /// Term = Fact { '*' Fact | '/' Fact } private ISymbolicExpressionTreeNode ParseTerm(Queue tokens) { var factors = new List(); var firstFactor = ParseFact(tokens); factors.Add(firstFactor); var next = tokens.Peek(); while (next.strVal == "*" || next.strVal == "/") { switch (next.strVal) { case "*": { tokens.Dequeue(); var fact = ParseFact(tokens); factors.Add(fact); break; } case "/": { tokens.Dequeue(); var invFact = ParseFact(tokens); var divNode = GetSymbol("/").CreateTreeNode(); // 1/x divNode.AddSubtree(invFact); factors.Add(divNode); break; } } next = tokens.Peek(); } if (factors.Count == 1) return factors.First(); else { var prod = GetSymbol("*").CreateTreeNode(); foreach (var f in factors) prod.AddSubtree(f); return prod; } } // Fact = SimpleFact ['^' SimpleFact] private ISymbolicExpressionTreeNode ParseFact(Queue tokens) { var expr = ParseSimpleFact(tokens); var next = tokens.Peek(); if (next.TokenType == TokenType.Operator && next.strVal == "^") { tokens.Dequeue(); // skip; var p = GetSymbol("^").CreateTreeNode(); p.AddSubtree(expr); p.AddSubtree(ParseSimpleFact(tokens)); expr = p; } return expr; } /// SimpleFact = '(' Expr ')' /// | '{' Expr '}' /// | 'LAG' '(' varId ',' ['+' | '-' ] number ')' /// | funcId '(' ArgList ') /// | VarExpr /// | number /// ArgList = Expr { ',' Expr } /// VarExpr = varId OptFactorPart /// OptFactorPart = [ ('=' varVal | '[' ['+' | '-' ] number {',' ['+' | '-' ] number } ']' ) ] /// varId = ident | ' ident ' | " ident " /// varVal = ident | ' ident ' | " ident " /// ident = '_' | letter { '_' | letter | digit } private ISymbolicExpressionTreeNode ParseSimpleFact(Queue tokens) { var next = tokens.Peek(); if (next.TokenType == TokenType.LeftPar) { var initPar = tokens.Dequeue(); // match par type var expr = ParseExpr(tokens); var rPar = tokens.Dequeue(); if (rPar.TokenType != TokenType.RightPar) throw new ArgumentException("expected closing parenthesis"); if (initPar.strVal == "(" && rPar.strVal == "}") throw new ArgumentException("expected closing )"); if (initPar.strVal == "{" && rPar.strVal == ")") throw new ArgumentException("expected closing }"); return expr; } else if (next.TokenType == TokenType.Identifier) { var idTok = tokens.Dequeue(); if (tokens.Peek().TokenType == TokenType.LeftPar) { // function identifier or LAG var funcId = idTok.strVal.ToUpperInvariant(); var funcNode = GetSymbol(funcId).CreateTreeNode(); var lPar = tokens.Dequeue(); if (lPar.TokenType != TokenType.LeftPar) throw new ArgumentException("expected ("); // handle 'lag' specifically if (funcNode.Symbol is LaggedVariable) { var varId = tokens.Dequeue(); if (varId.TokenType != TokenType.Identifier) throw new ArgumentException("Identifier expected. Format for lagged variables: \"lag(x, -1)\""); var comma = tokens.Dequeue(); if (comma.TokenType != TokenType.Comma) throw new ArgumentException("',' expected, Format for lagged variables: \"lag(x, -1)\""); double sign = 1.0; if (tokens.Peek().strVal == "+" || tokens.Peek().strVal == "-") { // read sign var signTok = tokens.Dequeue(); if (signTok.strVal == "-") sign = -1.0; } var lagToken = tokens.Dequeue(); if (lagToken.TokenType != TokenType.Number) throw new ArgumentException("Number expected, Format for lagged variables: \"lag(x, -1)\""); if (!lagToken.doubleVal.IsAlmost(Math.Round(lagToken.doubleVal))) throw new ArgumentException("Time lags must be integer values"); var laggedVarNode = funcNode as LaggedVariableTreeNode; laggedVarNode.VariableName = varId.strVal; laggedVarNode.Lag = (int)Math.Round(sign * lagToken.doubleVal); laggedVarNode.Weight = 1.0; } else { // functions var args = ParseArgList(tokens); // check number of arguments if (funcNode.Symbol.MinimumArity > args.Length || funcNode.Symbol.MaximumArity < args.Length) { throw new ArgumentException(string.Format("Symbol {0} requires between {1} and {2} arguments.", funcId, funcNode.Symbol.MinimumArity, funcNode.Symbol.MaximumArity)); } foreach (var arg in args) funcNode.AddSubtree(arg); } var rPar = tokens.Dequeue(); if (rPar.TokenType != TokenType.RightPar) throw new ArgumentException("expected )"); return funcNode; } else { // variable if (tokens.Peek().TokenType == TokenType.Eq) { // binary factor tokens.Dequeue(); // skip Eq var valTok = tokens.Dequeue(); if (valTok.TokenType != TokenType.Identifier) throw new ArgumentException("expected identifier"); var binFactorNode = (BinaryFactorVariableTreeNode)binaryFactorVar.CreateTreeNode(); binFactorNode.Weight = 1.0; binFactorNode.VariableName = idTok.strVal; binFactorNode.VariableValue = valTok.strVal; return binFactorNode; } else if (tokens.Peek().TokenType == TokenType.LeftBracket) { // factor variable var factorVariableNode = (FactorVariableTreeNode)factorVar.CreateTreeNode(); factorVariableNode.VariableName = idTok.strVal; tokens.Dequeue(); // skip [ var weights = new List(); // at least one weight is necessary var sign = 1.0; if (tokens.Peek().TokenType == TokenType.Operator) { var opToken = tokens.Dequeue(); if (opToken.strVal == "+") sign = 1.0; else if (opToken.strVal == "-") sign = -1.0; else throw new ArgumentException(); } if (tokens.Peek().TokenType != TokenType.Number) throw new ArgumentException("number expected"); var weightTok = tokens.Dequeue(); weights.Add(sign * weightTok.doubleVal); while (tokens.Peek().TokenType == TokenType.Comma) { // skip comma tokens.Dequeue(); if (tokens.Peek().TokenType == TokenType.Operator) { var opToken = tokens.Dequeue(); if (opToken.strVal == "+") sign = 1.0; else if (opToken.strVal == "-") sign = -1.0; else throw new ArgumentException(); } weightTok = tokens.Dequeue(); if (weightTok.TokenType != TokenType.Number) throw new ArgumentException("number expected"); weights.Add(sign * weightTok.doubleVal); } var rightBracketToken = tokens.Dequeue(); if (rightBracketToken.TokenType != TokenType.RightBracket) throw new ArgumentException("closing bracket ] expected"); factorVariableNode.Weights = weights.ToArray(); return factorVariableNode; } else { // variable var varNode = (VariableTreeNode)variable.CreateTreeNode(); varNode.Weight = 1.0; varNode.VariableName = idTok.strVal; return varNode; } } } else if (next.TokenType == TokenType.Number) { var numTok = tokens.Dequeue(); var constNode = (ConstantTreeNode)constant.CreateTreeNode(); constNode.Value = numTok.doubleVal; return constNode; } else { throw new ArgumentException(string.Format("unexpected token in expression {0}", next.strVal)); } } // ArgList = Expr { ',' Expr } private ISymbolicExpressionTreeNode[] ParseArgList(Queue tokens) { var exprList = new List(); exprList.Add(ParseExpr(tokens)); while (tokens.Peek().TokenType != TokenType.RightPar) { var comma = tokens.Dequeue(); if (comma.TokenType != TokenType.Comma) throw new ArgumentException("expected ',' "); exprList.Add(ParseExpr(tokens)); } return exprList.ToArray(); } } }