├── CustomFunction.cs ├── Exception.cs ├── Expression.cs ├── ExpressionSolver.cs ├── README.md ├── SolverTools.cs ├── Symbol.cs ├── SymbolList.cs ├── Tests └── ExpressionSolverTests.cs ├── ValidityChecker.cs └── Variable.cs /CustomFunction.cs: -------------------------------------------------------------------------------- 1 | namespace AK 2 | { 3 | 4 | public class CustomFunction 5 | { 6 | public string name; 7 | public System.Func funcmd; 8 | public System.Func funcmo; 9 | public System.Func func1d; 10 | public int paramCount; 11 | public bool enableSymbolicationTimeEvaluation; 12 | 13 | public CustomFunction(string name, int paramCount, System.Func func, bool enableSymbolicationTimeEvaluation) 14 | { 15 | this.funcmd = func; 16 | this.enableSymbolicationTimeEvaluation = enableSymbolicationTimeEvaluation; 17 | this.paramCount = paramCount; 18 | this.name = name; 19 | } 20 | 21 | public CustomFunction(string name, int paramCount, System.Func func, bool enableSymbolicationTimeEvaluation) 22 | { 23 | this.funcmo = func; 24 | this.enableSymbolicationTimeEvaluation = enableSymbolicationTimeEvaluation; 25 | this.paramCount = paramCount; 26 | this.name = name; 27 | } 28 | 29 | public CustomFunction(string name, System.Func func, bool enableSymbolicationTimeEvaluation) 30 | { 31 | this.func1d = func; 32 | this.enableSymbolicationTimeEvaluation = enableSymbolicationTimeEvaluation; 33 | this.paramCount = 1; 34 | this.name = name; 35 | } 36 | 37 | public double Invoke(double[] p) 38 | { 39 | return funcmd(p); 40 | } 41 | 42 | public double Invoke(object[] p) 43 | { 44 | return funcmo(p); 45 | } 46 | 47 | public double Invoke(double x) 48 | { 49 | return func1d != null ? func1d(x) : funcmd(new double[]{x}); 50 | } 51 | 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /Exception.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | 4 | namespace AK 5 | { 6 | public class ESSyntaxErrorException : System.Exception 7 | { 8 | public ESSyntaxErrorException(string msg) : base(msg) {} 9 | } 10 | 11 | public class ESInvalidNameException : System.Exception 12 | { 13 | public ESInvalidNameException(string msg) : base(msg) {} 14 | } 15 | 16 | public class ESParameterTypeChangedException : System.Exception 17 | { 18 | public ESParameterTypeChangedException(string msg) : base(msg) {} 19 | } 20 | 21 | public class ESTooManyParametersException : System.Exception 22 | { 23 | public ESTooManyParametersException(string msg) : base(msg) {} 24 | } 25 | 26 | public class ESInvalidCharacterException : System.Exception 27 | { 28 | public ESInvalidCharacterException(string msg) : base(msg) {} 29 | } 30 | 31 | public class ESInvalidFunctionNameException : System.Exception 32 | { 33 | public ESInvalidFunctionNameException(string msg) : base(msg) {} 34 | } 35 | 36 | public class ESInvalidParametersException : System.Exception 37 | { 38 | public ESInvalidParametersException(string msg) : base(msg) {} 39 | } 40 | 41 | public class ESUnknownExpressionException : System.Exception 42 | { 43 | public ESUnknownExpressionException(string msg) : base(msg) {} 44 | } 45 | } -------------------------------------------------------------------------------- /Expression.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | 4 | namespace AK 5 | { 6 | public class Expression 7 | { 8 | public Symbol root; 9 | public Dictionary constants = new Dictionary(); 10 | 11 | public Variable SetVariable(string name, double value) 12 | { 13 | Variable v; 14 | if (constants.TryGetValue(name,out v)) 15 | { 16 | if (v.stringValue != null) 17 | { 18 | throw new ESParameterTypeChangedException("Can not change type of existing parameter " + name); 19 | } 20 | v.value = value; 21 | return v; 22 | } 23 | v = new Variable(name,value); 24 | constants.Add(name,v); 25 | return v; 26 | } 27 | 28 | public Variable SetVariable(string name, string value) 29 | { 30 | Variable v; 31 | if (constants.TryGetValue(name,out v)) 32 | { 33 | if (v.stringValue == null) 34 | { 35 | throw new ESParameterTypeChangedException("Can not change type of existing parameter " + name); 36 | } 37 | v.stringValue = value; 38 | return v; 39 | } 40 | v = new Variable(name,value); 41 | constants.Add(name,v); 42 | return v; 43 | } 44 | 45 | /// 46 | /// Get reference to a variable that has been set up already. 47 | /// 48 | /// The variable or null if variable is not found. 49 | /// Name of the variable. 50 | public Variable GetVariable(string name) 51 | { 52 | return constants[name]; 53 | } 54 | 55 | public override string ToString() 56 | { 57 | if (root.type == SymbolType.SubExpression) 58 | { 59 | var s = root.ToString(); 60 | return s.Substring(1,s.Length-2); 61 | } 62 | else 63 | { 64 | return root.ToString(); 65 | } 66 | } 67 | 68 | public double Evaluate() 69 | { 70 | return ExpressionSolver.GetSymbolValue(root); 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /ExpressionSolver.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | 4 | namespace AK 5 | { 6 | /* 7 | 8 | ExpressionSolver is licensed under the MIT license. 9 | 10 | Copyright (c) 2015 Antti Kuukka 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining a copy 13 | of this software and associated documentation files (the "Software"), to deal 14 | in the Software without restriction, including without limitation the rights 15 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | copies of the Software, and to permit persons to whom the Software is 17 | furnished to do so, subject to the following conditions: 18 | 19 | 20 | 21 | The above copyright notice and this permission notice shall be included in 22 | all copies or substantial portions of the Software. 23 | 24 | 25 | 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 32 | THE SOFTWARE. 33 | 34 | */ 35 | 36 | public class ExpressionSolver 37 | { 38 | private static readonly int MaxCustomFunctionParamCount = 8; 39 | 40 | public enum UndefinedVariablePolicy 41 | { 42 | Error, 43 | DefineGlobalVariable, 44 | DefineExpressionLocalVariable 45 | } 46 | 47 | public UndefinedVariablePolicy undefinedVariablePolicy; 48 | 49 | private static Dictionary immutableGlobalConstants = new Dictionary() 50 | { 51 | {"e",System.Math.E}, 52 | {"pi",System.Math.PI} 53 | }; 54 | private Dictionary globalConstants = new Dictionary(); 55 | private Dictionary customFuncs = new Dictionary(); 56 | 57 | public ExpressionSolver() 58 | { 59 | undefinedVariablePolicy = UndefinedVariablePolicy.Error; 60 | AddCustomFunction("sin", delegate(double p) { return System.Math.Sin(p); },true); 61 | AddCustomFunction("cos", delegate(double p) { return System.Math.Cos(p); },true); 62 | AddCustomFunction("tan", delegate(double p) { return System.Math.Tan(p); },true); 63 | AddCustomFunction("abs", delegate(double p) { return System.Math.Abs(p); },true); 64 | AddCustomFunction("asin", delegate(double p) { return System.Math.Asin(p); },true); 65 | AddCustomFunction("acos", delegate(double p) { return System.Math.Acos(p); },true); 66 | AddCustomFunction("atan", delegate(double p) { return System.Math.Atan(p); },true); 67 | AddCustomFunction("atan2",2, delegate(double[] p) { return System.Math.Atan2(p[0],p[1]); },true); 68 | AddCustomFunction("sqrt", delegate(double p) { return System.Math.Sqrt(p); },true); 69 | AddCustomFunction("sign", delegate(double p) { return System.Math.Sign(p); },true); 70 | AddCustomFunction("floor", delegate(double p) { return System.Math.Floor(p); },true); 71 | AddCustomFunction("ceil", delegate(double p) { return System.Math.Ceiling(p); },true); 72 | AddCustomFunction("min",2, delegate(double[] p) { return System.Math.Min(p[0],p[1]); },true); 73 | AddCustomFunction("max",2, delegate(double[] p) { return System.Math.Max(p[0],p[1]); },true); 74 | AddCustomFunction("sinh", delegate(double p) { return System.Math.Sinh(p); },true); 75 | AddCustomFunction("exp", delegate(double p) { return System.Math.Exp(p); },true); 76 | AddCustomFunction("cosh", delegate(double p) { return System.Math.Cosh(p); },true); 77 | AddCustomFunction("tanh", delegate(double p) { return System.Math.Tanh(p); },true); 78 | AddCustomFunction("log", delegate(double p) { return System.Math.Log(p); },true); 79 | AddCustomFunction("log10", delegate(double p) { return System.Math.Log10(p); },true); 80 | AddCustomFunction("round", delegate(double p) { return System.Math.Round(p); },true); 81 | } 82 | 83 | public void AddCustomFunction(string name, int paramCount, System.Func func, bool enableSymbolicationTimeEvaluation = false) 84 | { 85 | if (paramCount>MaxCustomFunctionParamCount) 86 | { 87 | throw new ESTooManyParametersException("Custom functions can have no more than " + MaxCustomFunctionParamCount + " parameters"); 88 | } 89 | customFuncs[name] = new CustomFunction(name, paramCount, func, enableSymbolicationTimeEvaluation); 90 | } 91 | 92 | public void AddCustomFunction(string name, int paramCount, System.Func func, bool enableSymbolicationTimeEvaluation = false) 93 | { 94 | if (paramCount>MaxCustomFunctionParamCount) 95 | { 96 | throw new ESTooManyParametersException("Custom functions can have no more than " + MaxCustomFunctionParamCount + " parameters"); 97 | } 98 | customFuncs[name] = new CustomFunction(name, paramCount, func, enableSymbolicationTimeEvaluation); 99 | } 100 | 101 | public void AddCustomFunction(string name, System.Func func, bool enableSymbolicationTimeEvaluation = false) 102 | { 103 | customFuncs[name] = new CustomFunction(name, func, enableSymbolicationTimeEvaluation); 104 | } 105 | 106 | public void RemoveCustomFunction(string name) 107 | { 108 | customFuncs.Remove (name); 109 | } 110 | 111 | public Variable GetGlobalVariable(string name) 112 | { 113 | return globalConstants[name]; 114 | } 115 | 116 | public Variable SetGlobalVariable(string name, double value) 117 | { 118 | Variable variable; 119 | if (globalConstants.TryGetValue(name, out variable)) 120 | { 121 | if (variable.stringValue != null) 122 | { 123 | throw new ESParameterTypeChangedException("Can not change type of existing parameter " + name); 124 | } 125 | variable.value = value; 126 | return variable; 127 | } 128 | else 129 | { 130 | Variable v = new Variable(name,value); 131 | globalConstants.Add (name,v); 132 | return v; 133 | } 134 | } 135 | 136 | public Variable SetGlobalVariable(string name, string value) 137 | { 138 | if (value == null) 139 | { 140 | throw new System.ArgumentException("Null is not acceptable string parameter value."); 141 | } 142 | Variable variable; 143 | if (globalConstants.TryGetValue(name, out variable)) 144 | { 145 | if (variable.stringValue == null) 146 | { 147 | throw new ESParameterTypeChangedException("Can not change type of existing parameter " + name); 148 | } 149 | variable.stringValue = value; 150 | return variable; 151 | } 152 | else 153 | { 154 | Variable v = new Variable(name,value); 155 | globalConstants.Add (name,v); 156 | return v; 157 | } 158 | } 159 | 160 | public bool RemoveGlobalVariable(string name) 161 | { 162 | return globalConstants.Remove(name); 163 | } 164 | 165 | public void ClearGlobalVariables() 166 | { 167 | globalConstants.Clear(); 168 | } 169 | 170 | public double EvaluateExpression(string formula) 171 | { 172 | return SymbolicateExpression(formula,(string[])null).Evaluate(); 173 | } 174 | 175 | public Expression SymbolicateExpression(string formula, string localVariable) 176 | { 177 | return SymbolicateExpression(formula,new string[1]{localVariable}); 178 | } 179 | 180 | public Expression SymbolicateExpression(string formula, string[] localVariables = null) { 181 | Expression newExpression = new Expression(); 182 | if (localVariables != null) 183 | { 184 | foreach (var localVariableName in localVariables) 185 | { 186 | if (localVariableName[0] == '$') 187 | { 188 | newExpression.SetVariable(localVariableName.Substring(1,localVariableName.Length-1).Trim(),""); 189 | } 190 | else 191 | { 192 | newExpression.SetVariable(localVariableName.Trim(),0.0); 193 | } 194 | } 195 | } 196 | 197 | formula = SolverTools.RemoveWhiteSpace(formula); 198 | 199 | // Check validity 200 | try 201 | { 202 | ValidityChecker.CheckValidity(formula); 203 | } 204 | catch (System.Exception ex) 205 | { 206 | throw ex; 207 | } 208 | 209 | Symbol s = Symbolicate(formula, 0, formula.Length,newExpression); 210 | newExpression.root = s; 211 | return newExpression; 212 | } 213 | 214 | static double ParseSymbols(SymbolList syms) 215 | { 216 | bool transformNextValue = false; 217 | double sum = 0; 218 | double curTerm = 0; 219 | 220 | SymbolType prevOper = SymbolType.OperatorAdd; 221 | 222 | int len = syms.symbols.Count; 223 | var symbolList = syms.symbols; 224 | 225 | for (int i=0;i parameters = SolverTools.ParseParameters(formula,i,end); 418 | SymbolList newSubExpression = new SymbolList(); 419 | newSubExpression.Append(new Symbol(customFunc)); 420 | for (int k=0;k begin && (formula[i] == '*' || formula[i] == '/'))) 494 | { 495 | numValues++; 496 | 497 | // Unless we are dealing with a monome, symbolicate the term 498 | Symbol newSymbol = SymbolicateValue(formula, formula[currentTermBegin] == '-' ? currentTermBegin + 1 : currentTermBegin, i,exp); 499 | // Check if we can simplify the generated symbol 500 | if (newSymbol.IsImmutableConstant() && newSymbol.IsRealValueType()) 501 | { 502 | // Constants are multiplied/divided together 503 | if (divideNext) 504 | constMultiplier /= GetSymbolValue(newSymbol); 505 | else 506 | constMultiplier *= GetSymbolValue(newSymbol); 507 | constMultiplierUsed = true; 508 | } 509 | else 510 | { 511 | if (divideNext) 512 | symbols.Append(new Symbol(SymbolType.OperatorDivide)); 513 | newSymbol.Simplify(); 514 | symbols.Append(newSymbol); 515 | } 516 | 517 | if (i == end) { 518 | break; 519 | } 520 | divideNext = formula[i] == '/'; 521 | currentTermBegin = i + 1; 522 | } 523 | else if (formula[i] == '(') 524 | { 525 | currentDepth++; 526 | } 527 | else if (formula[i] == ')') 528 | { 529 | currentDepth--; 530 | } 531 | else if (formula[i] == '-' && currentDepth == 0 && !(i>begin && formula[i-1] == '^') ) 532 | { 533 | sign++; 534 | } 535 | } 536 | 537 | // If the generated monome has negative number of minus signs, then we append *-1 to end of the list, or if the preceding symbol is constant real number that is part of a monome, we multiply it. 538 | if (sign % 2 == 1) 539 | { 540 | constMultiplier =-constMultiplier; 541 | } 542 | if (constMultiplierUsed || sign % 2 == 1) 543 | { 544 | // Add the const multiplier to the expression 545 | if (symbols.Length>0 && symbols.first.type==SymbolType.OperatorDivide) 546 | { 547 | // Put to the begin of the expression we are building 548 | symbols.symbols.Insert(0,new Symbol(constMultiplier)); 549 | } 550 | else if (symbols.Length > 0 && symbols.last.type == SymbolType.SubExpression && symbols.last.IsMonome()) 551 | { 552 | // Add inside the last subexpression 553 | SymbolList leftSideExpression = symbols.last.subExpression; 554 | if (leftSideExpression.last.type==SymbolType.RealValue && leftSideExpression.last.IsImmutableConstant()) 555 | { 556 | leftSideExpression.SetSymbolAtIndex(leftSideExpression.Length-1,new Symbol(leftSideExpression.last.value*constMultiplier)); 557 | } 558 | else 559 | { 560 | leftSideExpression.Append(new Symbol(constMultiplier)); 561 | } 562 | } 563 | else 564 | { 565 | // Put to the end of the expression we are building 566 | symbols.Append(new Symbol(constMultiplier)); 567 | } 568 | } 569 | 570 | // Check if the final monome is just a real number, in which case we don't have to return a subexpression type 571 | if (symbols.Length == 1 && symbols.first.IsImmutableConstant() && symbols.first.IsRealValueType()) 572 | { 573 | return symbols.first.type == SymbolType.RealValue ? symbols.first : new Symbol(GetSymbolValue(symbols.first)); 574 | } 575 | Symbol s = new Symbol(SymbolType.SubExpression); 576 | s.subExpression = symbols; 577 | s.Simplify(); 578 | return s; 579 | } 580 | 581 | Symbol Symbolicate(string formula, int begin, int end, Expression exp) 582 | { 583 | var symbols = new SymbolList(); 584 | 585 | int i = begin - 1; 586 | int currentTermBegin = formula[begin] == '+' ? begin + 1 : begin; 587 | int currentDepth = 0; 588 | 589 | for (;;) 590 | { 591 | i++; 592 | if (i == end || (currentDepth == 0 && i > begin && (formula[i - 1] != '*' && formula[i - 1] != '/') && (formula[i] == '+' || formula[i] == '-'))) 593 | { 594 | symbols.Append(SymbolicateMonome(formula, currentTermBegin, i,exp)); 595 | if (i == end) 596 | { 597 | break; 598 | } 599 | else { 600 | // The sign of the term is included in the next monome only if its minus 601 | currentTermBegin = (formula[i] == '-') ? i : i + 1; 602 | symbols.Append(new Symbol(SymbolType.OperatorAdd)); 603 | } 604 | } 605 | else if (formula[i] == '(') 606 | { 607 | currentDepth++; 608 | } 609 | else if (formula[i] == ')') 610 | { 611 | currentDepth--; 612 | } 613 | else if (formula[i] == '^') 614 | { 615 | i = SolverTools.ParseUntilEndOfExponent(formula,i+1,end) - 1; 616 | } 617 | } 618 | 619 | // If at this point we only have one real number left, just return it as a simple value. 620 | if (symbols.Length == 1 && symbols.first.type == SymbolType.RealValue) 621 | { 622 | return symbols.first; 623 | } 624 | 625 | // We don't have that single expression, but: 626 | // Now that we are here, we have symbol list which consists of only addition operators and value types. This is a great place to sum constant values together! 627 | double constantSum = 0; 628 | bool addedConstants = false; 629 | 630 | for (int j = 0; j < symbols.Length; j++) 631 | { 632 | Symbol s = symbols.getSymbol(j); 633 | if (s.IsImmutableConstant() && s.IsRealValueType()) { 634 | constantSum += s.value; 635 | addedConstants = true; 636 | if (j == symbols.Length - 1) 637 | { 638 | // Destroy preceding + 639 | symbols.symbols.RemoveAt (j); 640 | break; 641 | } 642 | symbols.symbols.RemoveAt(j); 643 | symbols.symbols.RemoveAt(j); 644 | j--; 645 | } 646 | else 647 | { 648 | // Skip the following + symbol 649 | j++; 650 | } 651 | } 652 | if (addedConstants) 653 | { 654 | if (symbols.Length > 0 && symbols.getSymbol(symbols.Length - 1).IsRealValueType()) 655 | { 656 | symbols.Append(new Symbol(SymbolType.OperatorAdd)); 657 | } 658 | symbols.Append(new Symbol(constantSum)); 659 | } 660 | 661 | // Finally, if the symbolicated sum is just a single real number, even varying, return just a simple symbol 662 | if (symbols.Length == 1 && symbols.getSymbol(0).type == SymbolType.RealValue) 663 | { 664 | Symbol s = symbols.getSymbol(0); 665 | return s; 666 | } 667 | 668 | // Optimization: get rid of unnecessary jumps to subexpressions 669 | for (int j=0;j ParseParameters(string formula, int begin, int end) 56 | { 57 | List r = new List(); 58 | 59 | int currentParamBegin = -1; 60 | int depth = 0; 61 | 62 | for (int i=begin;ibegin && formula[i]=='-') 114 | return i; 115 | else if (i>begin && formula[i]=='+') 116 | return i; 117 | } 118 | } 119 | return end; 120 | } 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /Symbol.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | namespace AK 4 | { 5 | 6 | public enum SymbolType 7 | { 8 | Empty, 9 | RealValue, 10 | OperatorAdd, 11 | OperatorMultiply, 12 | OperatorDivide, 13 | SubExpression, 14 | Pow, 15 | StringLiteral, 16 | StringVariable, 17 | FuncCustom, 18 | }; 19 | 20 | public class Symbol 21 | { 22 | public SymbolType type; 23 | public double _value; 24 | public object ptr; 25 | 26 | public Variable variable 27 | { 28 | get { 29 | return (Variable)ptr; 30 | } 31 | 32 | set 33 | { 34 | ptr = value; 35 | } 36 | } 37 | 38 | public string stringValue 39 | { 40 | get { 41 | return type == SymbolType.StringLiteral ? (string)ptr : ((Variable)ptr).stringValue; 42 | } 43 | 44 | set 45 | { 46 | ptr = value; 47 | } 48 | } 49 | 50 | public SymbolList subExpression 51 | { 52 | get 53 | { 54 | return (SymbolList)ptr; 55 | } 56 | 57 | set 58 | { 59 | ptr = value; 60 | } 61 | } 62 | 63 | public CustomFunction customFunc 64 | { 65 | get 66 | { 67 | return (CustomFunction)ptr; 68 | } 69 | 70 | set 71 | { 72 | ptr = value; 73 | } 74 | } 75 | 76 | // Test if value of the symbol is independent of variables. 77 | public bool IsImmutableConstant() 78 | { 79 | if (type == SymbolType.RealValue) 80 | { 81 | if (variable != null) 82 | return false; 83 | return true; 84 | } 85 | else if (type == SymbolType.StringLiteral) 86 | { 87 | return true; 88 | } 89 | else if (type == SymbolType.SubExpression) 90 | { 91 | return IsSymbolListImmutableConstant(subExpression); 92 | } 93 | else 94 | { 95 | return false; 96 | } 97 | } 98 | 99 | public bool IsMonome() 100 | { 101 | var s = this; 102 | if (s.type==SymbolType.RealValue) 103 | { 104 | return true; 105 | } 106 | else if (s.type==SymbolType.SubExpression) 107 | { 108 | var syms = s.subExpression; 109 | for (int i=0;i (x) 124 | if (type == SymbolType.SubExpression) 125 | { 126 | if (subExpression.Length != 1) 127 | { 128 | return; 129 | } 130 | if (subExpression.first.type == SymbolType.SubExpression) 131 | { 132 | // Get pointer to sub-subexpression 133 | SymbolList subSubExpression = subExpression.symbols[0].subExpression; 134 | subExpression = subSubExpression; 135 | } 136 | else if (subExpression.first.type == SymbolType.RealValue || subExpression.first.type == SymbolType.StringLiteral || subExpression.first.type == SymbolType.StringVariable) 137 | { 138 | // We have single real number surrounded by parenthesis, it can become a real number 139 | CopyValuesFrom(subExpression.first); 140 | } 141 | } 142 | } 143 | 144 | private bool IsSymbolListImmutableConstant(SymbolList l) 145 | { 146 | var len = l.Length; 147 | for (int k = 0; k < len; k++) 148 | { 149 | var s = l.getSymbol(k); 150 | if (s.type == SymbolType.RealValue) 151 | { 152 | if (s.variable != null) 153 | { 154 | return false; 155 | } 156 | } 157 | else if (s.type == SymbolType.FuncCustom && !s.customFunc.enableSymbolicationTimeEvaluation) 158 | { 159 | return false; 160 | } 161 | else if (s.type == SymbolType.StringVariable) 162 | { 163 | return false; 164 | } 165 | else if (s.type == SymbolType.SubExpression) 166 | { 167 | if (!IsSymbolListImmutableConstant(s.subExpression)) 168 | { 169 | return false; 170 | } 171 | } 172 | } 173 | return true; 174 | } 175 | 176 | public void CopyValuesFrom(Symbol o) 177 | { 178 | type = o.type; 179 | _value = o._value; 180 | ptr = o.ptr; 181 | } 182 | 183 | public double value 184 | { 185 | get 186 | { 187 | return variable == null ? _value : ((Variable)ptr).value; 188 | } 189 | } 190 | 191 | public bool IsRealValueType() 192 | { 193 | return type == SymbolType.RealValue || type == SymbolType.SubExpression; 194 | } 195 | 196 | public bool IsStringType() 197 | { 198 | return type == SymbolType.StringLiteral || type == SymbolType.StringVariable; 199 | } 200 | 201 | public Symbol(SymbolType type, double va) 202 | { 203 | this.type = type; 204 | _value = va; 205 | variable = null; 206 | } 207 | 208 | public Symbol(SymbolType type) 209 | { 210 | this.type = type; 211 | } 212 | 213 | public Symbol(SymbolList subExpression) 214 | { 215 | this.type = SymbolType.SubExpression; 216 | this.subExpression = subExpression; 217 | } 218 | 219 | public Symbol(double value) 220 | { 221 | type = SymbolType.RealValue; 222 | _value = value; 223 | } 224 | 225 | public Symbol(string stringValue) 226 | { 227 | type = SymbolType.StringLiteral; 228 | ptr = stringValue; 229 | } 230 | 231 | public Symbol(Variable ptrToConstValue) 232 | { 233 | type = ptrToConstValue.stringValue != null ? SymbolType.StringVariable : SymbolType.RealValue; 234 | variable = ptrToConstValue; 235 | } 236 | 237 | public Symbol(CustomFunction func) 238 | { 239 | type = SymbolType.FuncCustom; 240 | customFunc = func; 241 | } 242 | 243 | public override string ToString() 244 | { 245 | switch (type) 246 | { 247 | case SymbolType.RealValue: 248 | case SymbolType.StringVariable: 249 | if (variable != null) 250 | { 251 | return variable.name; 252 | } 253 | return _value.ToString(); 254 | case SymbolType.OperatorAdd: 255 | return "+"; 256 | case SymbolType.StringLiteral: 257 | return "\'" + stringValue + "\'"; 258 | case SymbolType.OperatorMultiply: 259 | return "*"; 260 | case SymbolType.OperatorDivide: 261 | return "/"; 262 | case SymbolType.Pow: 263 | return "pow"; 264 | case SymbolType.SubExpression: 265 | return "("+subExpression.ToString()+")"; 266 | case SymbolType.Empty: 267 | return "(null)"; 268 | case SymbolType.FuncCustom: 269 | return customFunc.name; 270 | } 271 | return ""; 272 | } 273 | } 274 | 275 | } 276 | -------------------------------------------------------------------------------- /SymbolList.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | 4 | namespace AK 5 | { 6 | public class SymbolList 7 | { 8 | public List symbols = new List(); 9 | 10 | public override string ToString () 11 | { 12 | var l = this; 13 | string r = ""; 14 | for (int i=0;i1) { 27 | for (int j=1;j0.0000001f) 12 | { 13 | throw new System.Exception("ExpressionSolverTest failed"); 14 | } 15 | } 16 | 17 | public static void AssertSameValue(string s1, string s2) 18 | { 19 | if (s1 != s2) 20 | { 21 | UnityEngine.Debug.Log(s1 + " vs " + s2); 22 | throw new System.Exception("ExpressionSolverTest failed"); 23 | } 24 | } 25 | 26 | public static void Assert(bool cond) 27 | { 28 | if (!cond) 29 | { 30 | throw new System.Exception("ExpressionSolverTest failed"); 31 | } 32 | } 33 | 34 | public static void TestComplexFormulas() 35 | { 36 | ExpressionSolver solver = new ExpressionSolver(); 37 | { 38 | var exp = solver.SymbolicateExpression("sin(1/(0.5*x))","x"); 39 | var x = 21.0; 40 | exp.SetVariable("x",x); 41 | AssertSameValue(exp.Evaluate(),System.Math.Sin(1/(0.5*x)) ); 42 | } 43 | { 44 | var exp = solver.SymbolicateExpression("(1+1)+.5"); 45 | AssertSameValue(exp.Evaluate(),2.5f); 46 | } 47 | { 48 | var exp = solver.SymbolicateExpression("sin(2/(1-0.5*x))","x"); 49 | var x = 21.0; 50 | exp.SetVariable("x",x); 51 | AssertSameValue(exp.Evaluate(),System.Math.Sin(2/(1-0.5*x)) ); 52 | } 53 | { 54 | var exp = solver.SymbolicateExpression("sin((2/(1-0.5*x))/2)","x"); 55 | var x = 21.0; 56 | exp.SetVariable("x",x); 57 | AssertSameValue(exp.Evaluate(),System.Math.Sin((2/(1-0.5*x))/2)); 58 | } 59 | { 60 | var exp = solver.SymbolicateExpression("1/(1+sin((2/(1-0.5*x))/2))","x"); 61 | var x = 21.0; 62 | exp.SetVariable("x",x); 63 | AssertSameValue(exp.Evaluate(),1/(1+System.Math.Sin((2/(1-0.5*x))/2))); 64 | } 65 | { 66 | var exp = solver.SymbolicateExpression(" (1-(3*(x/( (x+22)/x)-1*2*x))^0.5)/(x+1) ","x"); 67 | var x = -21.0; 68 | exp.SetVariable("x",x); 69 | AssertSameValue(exp.Evaluate(), (1-System.Math.Pow(3*(x/( (x+22)/x)-1*2*x),0.5))/(x+1) ); 70 | } 71 | { 72 | var exp = solver.SymbolicateExpression("(1+1/x)^x","x"); 73 | var x = 30000000.0; 74 | exp.SetVariable("x",x); 75 | AssertSameValue(exp.Evaluate(),System.Math.E); 76 | } 77 | } 78 | 79 | public static void Run() 80 | { 81 | TestNames(); 82 | TestComplexFormulas(); 83 | TestStringFuncs(); 84 | TestGlobalConstants(); 85 | TestExpLocalConstants(); 86 | TestUndefinedVariablePolicies(); 87 | TestSum(); 88 | TestFuncs(); 89 | TestWhiteSpaceRemoval(); 90 | } 91 | 92 | private static void TestNames() 93 | { 94 | ExpressionSolver solver = new ExpressionSolver(); 95 | try 96 | { 97 | solver.EvaluateExpression("0hakka"); 98 | throw new System.Exception("Test failed"); 99 | } 100 | catch (ESInvalidNameException) 101 | { 102 | // As expected 103 | } 104 | try 105 | { 106 | solver.EvaluateExpression("0.0.0"); 107 | throw new System.Exception("Test failed"); 108 | } 109 | catch (ESInvalidNameException) 110 | { 111 | // As expected 112 | } 113 | } 114 | 115 | public static void TestStringFuncs() 116 | { 117 | ExpressionSolver solver = new ExpressionSolver(); 118 | solver.AddCustomFunction("strlen",1, delegate(object[] p) { 119 | return ((string)p[0]).Length; 120 | },true); 121 | var exp = solver.SymbolicateExpression("strlen('123')"); 122 | AssertSameValue(exp.Evaluate(),3.0); 123 | exp = solver.SymbolicateExpression("strlen('12\\'3')"); 124 | AssertSameValue(exp.Evaluate(),4.0); 125 | exp = solver.SymbolicateExpression("strlen('12\\'3 4')"); 126 | AssertSameValue(exp.Evaluate(),6.0); 127 | solver.AddCustomFunction("strlen2",2, delegate(object[] p) { 128 | return ((string)p[0]).Length*(double)p[1]; 129 | }); 130 | exp = solver.SymbolicateExpression("strlen2('12\\'3 4',2.5)"); 131 | AssertSameValue(exp.Evaluate(),6.0*2.5); 132 | 133 | string[] erroneousStrings = new string[]{"strlen(''')","strlen('''')","''"}; 134 | foreach (var errorString in erroneousStrings) 135 | { 136 | try 137 | { 138 | exp = solver.SymbolicateExpression(errorString); 139 | throw new System.Exception("ExpressionSolverTest failed"); 140 | } 141 | catch (ESSyntaxErrorException) 142 | { 143 | // Parameters were not given correctly - syntax error expected 144 | } 145 | catch (System.Exception) 146 | { 147 | throw new System.Exception("ExpressionSolverTest failed"); 148 | } 149 | } 150 | 151 | // Because strlen should be evaluated at symbolication time, the following should reduce to one real value symbol: 152 | exp = solver.SymbolicateExpression("(1+strlen('123')+1)/strlen('12345')"); 153 | Assert(exp.root.type == SymbolType.RealValue); 154 | AssertSameValue(exp.root.value,1); 155 | // But if one of the parameters is not constant, then we cant do it: 156 | exp = solver.SymbolicateExpression("strlen('123')+x", new string[]{"x"}); 157 | Assert(exp.root.type == SymbolType.SubExpression); 158 | 159 | // Test string variables. Both exp-local and global 160 | exp = solver.SymbolicateExpression("strlen(stringVariableTest)","$stringVariableTest"); 161 | exp.SetVariable("stringVariableTest","test"); 162 | AssertSameValue(exp.Evaluate(),4); 163 | try 164 | { 165 | exp.SetVariable("stringVariableTest",121); 166 | Assert(false); 167 | } 168 | catch (ESParameterTypeChangedException) 169 | { 170 | } 171 | solver.SetGlobalVariable("striva","123"); 172 | exp = solver.SymbolicateExpression("strlen( ( striva ) )/3"); 173 | AssertSameValue(exp.Evaluate(),1); 174 | try 175 | { 176 | solver.SetGlobalVariable("striva",42141.0); 177 | Assert(false); 178 | } 179 | catch (ESParameterTypeChangedException) 180 | { 181 | } 182 | } 183 | 184 | public static void TestWhiteSpaceRemoval() 185 | { 186 | string formula = "ab cd"; 187 | AssertSameValue(SolverTools.RemoveWhiteSpace(formula),"abcd"); 188 | formula = "'ab cd'"; 189 | AssertSameValue(SolverTools.RemoveWhiteSpace(formula),"'ab cd'"); 190 | formula = " 'ab cd' "; 191 | AssertSameValue(SolverTools.RemoveWhiteSpace(formula),"'ab cd'"); 192 | formula = " 'ab\\' cd ' "; 193 | AssertSameValue(SolverTools.RemoveWhiteSpace(formula),"'ab\\' cd '"); 194 | } 195 | 196 | public static void TestFuncs() 197 | { 198 | ExpressionSolver solver = new ExpressionSolver(); 199 | solver.SetGlobalVariable("zero",0); 200 | var exp1 = solver.SymbolicateExpression("sin(pi/2)-cos(zero)"); 201 | AssertSameValue(exp1.Evaluate(),0); 202 | var exp2 = solver.SymbolicateExpression("2*e^zero - exp(zero)"); 203 | AssertSameValue(exp2.Evaluate(),1); 204 | var exp3 = solver.SymbolicateExpression("log(e^6)"); 205 | AssertSameValue(exp3.Evaluate(),6); 206 | var exp4 = solver.SymbolicateExpression("sqrt(2)-2^0.5"); 207 | AssertSameValue(exp4.Evaluate(),0); 208 | var exp5 = solver.SymbolicateExpression("exp(log(6))"); 209 | AssertSameValue(exp5.Evaluate(),6); 210 | var rnd = new System.Random(); 211 | solver.AddCustomFunction("Rnd1",2, delegate(double[] p) { 212 | return p[0] + (p[1]-p[0])*(rnd.NextDouble()); 213 | },false); 214 | var exp6 = solver.SymbolicateExpression("Rnd1(0,1)"); 215 | var firstRnd = exp6.Evaluate(); 216 | int iter = 0; 217 | while (true) 218 | { 219 | var secondRnd = exp6.Evaluate(); 220 | if (firstRnd != secondRnd) 221 | { 222 | break; 223 | } 224 | iter++; 225 | if (iter==10000) 226 | { 227 | // Probability of this happening is miniscule if everything works as it should 228 | throw new System.Exception("ExpressionSolverTest failed"); 229 | } 230 | } 231 | solver.AddCustomFunction("Rnd2",2, delegate(double[] p) { 232 | return p[0] + (p[1]-p[0])*(rnd.NextDouble()); 233 | },true); 234 | var exp7 = solver.SymbolicateExpression("Rnd2(0,1)"); 235 | AssertSameValue(exp7.Evaluate(),exp7.Evaluate()); 236 | var exp8 = solver.SymbolicateExpression("cos(0)+1*2"); 237 | AssertSameValue(exp8.Evaluate(),3); 238 | solver.AddCustomFunction("dist",5, delegate(double[] p) { 239 | return System.Math.Pow( (p[2]-p[0])*(p[2]-p[0]) + (p[3]-p[1])*(p[3]-p[1]) ,p[4]); 240 | },true); 241 | var exp9 = solver.SymbolicateExpression("dist(3*x,(4*x),+6*x,-1*x,sin(x))","x"); 242 | double x = 21; 243 | exp9.SetVariable("x",x); 244 | AssertSameValue(exp9.Evaluate(),System.Math.Pow( (3*x-6*x)*(3*x-6*x)+(4*x+x)*(4*x+x),System.Math.Sin(x) )); 245 | } 246 | 247 | public static void TestSum() 248 | { 249 | const int N = 10000; 250 | ExpressionSolver solver = new ExpressionSolver(); 251 | var exp = solver.SymbolicateExpression("1/2^i","i"); 252 | double sum = 0; 253 | for (int i=0;i acceptedPreceders = new Dictionary() 10 | { 11 | {'-',",*()/^0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_" }, 12 | {'+',",)0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_" }, 13 | {'/',")0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_" }, 14 | {'^',")0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_" }, 15 | {'*',")0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_" }, 16 | {')',"\')0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_" }, 17 | {'(',",*-+^/(0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_" }, 18 | {'\'',",(" } 19 | }; 20 | 21 | private static bool CanPrecede(char l, char r) 22 | { 23 | string accepted; 24 | if (acceptedPreceders.TryGetValue(r,out accepted)) { 25 | foreach (var c in accepted) 26 | { 27 | if (c==l) 28 | { 29 | return true; 30 | } 31 | } 32 | } 33 | return false; 34 | } 35 | 36 | private static void ThrowSyntaxErrorAt(string expression, int index) { 37 | int i = index; 38 | int l = expression.Length; 39 | int from = System.Math.Max(0,i-3); 40 | int to = System.Math.Min(l,i+4); 41 | int len = to-from; 42 | string str = "Syntax error: "; 43 | if (from>0) { 44 | str+="..."; 45 | } 46 | str += expression.Substring(from,len); 47 | if (to= '0' && c <= '9') 55 | return true; 56 | if (c == '(') 57 | return true; 58 | if (c == '-') 59 | return true; 60 | if (c == '\'') 61 | return true; 62 | if (c == '+') 63 | return true; 64 | if (c >= 'a' && c <='z') 65 | return true; 66 | if (c >= 'A' && c <='Z') 67 | return true; 68 | if (c >= '_') 69 | return true; 70 | if (c >= '.') 71 | return true; 72 | return false; 73 | } 74 | 75 | public static string CheckNamesAndConstants(string s) 76 | { 77 | var rege = @"(?>[_.a-zA-Z0-9]+)"; // ?> forces the regex engine to not backtrack, ?!\( ensures that the word does not end with ( which would make it a function name 78 | var matches = Regex.Matches(s,rege); 79 | if (matches.Count>0) 80 | { 81 | for (int i=0;i0 && !CanPrecede(expression[i-1],expression[i])) 130 | { 131 | UnityEngine.Debug.Log(expression[i]); 132 | UnityEngine.Debug.Log(expression[i-1]); 133 | ThrowSyntaxErrorAt(expression,i); 134 | } 135 | break; 136 | case ')': 137 | if (parenthesisDepth == 0) { 138 | throw new ESSyntaxErrorException("Parenthesis mismatch."); 139 | } 140 | if (i>0 && !CanPrecede(expression[i-1],expression[i])) { 141 | ThrowSyntaxErrorAt(expression,i); 142 | } 143 | parenthesisDepth--; 144 | break; 145 | case '/': 146 | case '*': 147 | case '+': 148 | case '^': 149 | case '-': 150 | if (i==l-1) 151 | ThrowSyntaxErrorAt(expression,i); 152 | if (i==0 && !(x=='-' || x=='+') ) 153 | ThrowSyntaxErrorAt(expression,i); 154 | if (!CanBeBeginOfRValue(expression[i+1])) { 155 | ThrowSyntaxErrorAt(expression,i); 156 | } 157 | if (i>0 && !CanPrecede(expression[i-1],expression[i])) { 158 | ThrowSyntaxErrorAt(expression,i); 159 | } 160 | if ( (x == '+' || x=='-') && i < l-2) { 161 | if ( (expression[i+2]=='+' || expression[i+2]=='-') && (expression[i+1]=='+' || expression[i+1]=='-') ) { 162 | ThrowSyntaxErrorAt(expression,i); 163 | } 164 | } 165 | break; 166 | case ',': 167 | if (i==l-1) 168 | ThrowSyntaxErrorAt(expression,i); 169 | if (!CanBeBeginOfRValue(expression[i+1])) { 170 | ThrowSyntaxErrorAt(expression,i); 171 | } 172 | break; 173 | case '\'': 174 | if (!inStringParam) 175 | { 176 | if (i == 0 || (i>0 && !CanPrecede(expression[i-1],expression[i]))) 177 | { 178 | ThrowSyntaxErrorAt(expression,i); 179 | } 180 | } 181 | else 182 | { 183 | if (i < l-1 && (expression[i+1] != ')' && expression[i+1] != ',')) 184 | { 185 | ThrowSyntaxErrorAt(expression,i); 186 | } 187 | } 188 | inStringParam = !inStringParam; 189 | break; 190 | case '.': 191 | if (i==l-1) 192 | ThrowSyntaxErrorAt(expression,i); 193 | if (! (expression[i+1] >= '0' && expression[i+1] <= '9') ) 194 | ThrowSyntaxErrorAt(expression,i); 195 | break; 196 | default: 197 | if (x >= '0' && x<= '9') 198 | break; 199 | if (x >= 'a' && x<= 'z') 200 | break; 201 | if (x >= 'A' && x<= 'Z') 202 | break; 203 | if (x == '_') 204 | break; 205 | throw new ESInvalidCharacterException(expression.Substring(i,1)); 206 | } 207 | } 208 | if (parenthesisDepth > 0) { 209 | throw new ESSyntaxErrorException("Parenthesis mismatch."); 210 | } 211 | if (inStringParam) { 212 | throw new ESSyntaxErrorException("String parameter not ending."); 213 | } 214 | var error = CheckNamesAndConstants(expression); 215 | if (error != null) 216 | { 217 | throw new ESInvalidNameException(error); 218 | } 219 | } 220 | 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /Variable.cs: -------------------------------------------------------------------------------- 1 | namespace AK 2 | { 3 | 4 | public class Variable 5 | { 6 | public double value; 7 | public string stringValue = null; 8 | 9 | public string name { get; private set; } 10 | 11 | public Variable(string name) 12 | { 13 | this.value=0; 14 | this.name = name; 15 | } 16 | 17 | public Variable(string name, double v) 18 | { 19 | this.value=v; 20 | this.name = name; 21 | } 22 | 23 | public Variable(string name, string s) 24 | { 25 | this.stringValue=s; 26 | this.name = name; 27 | } 28 | 29 | } 30 | 31 | } --------------------------------------------------------------------------------