// This function will go through a pre-compiled expression and resolve all periods (e.g. "yem#y + 300#y" => "yem#y + 300/12") void ResolvePeriods(ref Calculator_Expression exp) { if (exp is BinaryExpression) // if binary, look for footnote { if (exp._token.ID == TKID.FOOT) // if there is one, copy it to the parts and continue deeper { if ((exp as BinaryExpression)._rgt is VariableExpression) { string foot = ((exp as BinaryExpression)._rgt as VariableExpression)._name; if (periods.ContainsKey("#" + foot)) // got a period footnote { // consider better handling or better message if (!((exp as BinaryExpression)._lft is Calculator_Expression)) { throw new Exception("Period footnotes are only allowed on numbers."); } // reduce the usage count _vars[foot]--; // if no usage left, then remove variable if (_vars[foot] == 0) { _vars.Remove(foot); } exp = (exp as BinaryExpression)._lft; exp._token.Value = double.Parse(exp._token.Value.ToString()) * periods["#" + foot]; } else { // consider better handling or better message throw new Exception($"Unrecognised period footnote '{foot}'."); } } else // else dig deeper { ResolvePeriods(ref (exp as BinaryExpression)._lft); ResolvePeriods(ref (exp as BinaryExpression)._rgt); } } else // else dig deeper { ResolvePeriods(ref (exp as BinaryExpression)._lft); ResolvePeriods(ref (exp as BinaryExpression)._rgt); } } else if (exp is UnaryExpression) // if not binary, just dig deeper { ResolvePeriods(ref (exp as UnaryExpression)._expr); } else if (exp is FunctionExpression) { for (int i = 0; i < (exp as FunctionExpression)._parms.Count; i++) { Calculator_Expression expr = (exp as FunctionExpression)._parms[i]; ResolvePeriods(ref expr); (exp as FunctionExpression)._parms[i] = expr; } } }
// This function will go through a pre-compiled expression and propagate _level footnotes (e.g. "{yem + bch > 400}#1" => "{yem#1 + bch#1 > 400}") void PropagateFootnotes(ref Calculator_Expression exp) { if (exp is BinaryExpression) // if binary, look for footnote { if (exp._token.ID == TKID.FOOT) // if there is one, copy it to the parts and continue deeper { string foot = (exp as BinaryExpression)._rgt._token.Value.ToString(); exp = (exp as BinaryExpression)._lft; CopyFootnote(ref exp, foot); PropagateFootnotes(ref exp); } else // else dig deeper { PropagateFootnotes(ref (exp as BinaryExpression)._lft); PropagateFootnotes(ref (exp as BinaryExpression)._rgt); } } else if (exp is UnaryExpression) // if not binary, just dig deeper { PropagateFootnotes(ref (exp as UnaryExpression)._expr); } else if (exp is FunctionExpression) { for (int i = 0; i < (exp as FunctionExpression)._parms.Count; i++) { Calculator_Expression expr = (exp as FunctionExpression)._parms[i]; PropagateFootnotes(ref expr); (exp as FunctionExpression)._parms[i] = expr; } } }
public override Calculator_Expression Optimize() { _expr = _expr.Optimize(); return(_expr._token.Type == TKTYPE.LITERAL ? new Calculator_Expression(this.Evaluate(null)) : this); }
public override Calculator_Expression Optimize() { _lft = _lft.Optimize(); _rgt = _rgt.Optimize(); return(_lft._token.Type == TKTYPE.LITERAL && _rgt._token.Type == TKTYPE.LITERAL ? new Calculator_Expression(this.Evaluate(null)) : this); }
/// <summary> /// Evaluates an expression. /// </summary> /// <param name="operants">The formula variables with their values.</param> /// <param name="expression">The expression to evaluate.</param> /// <returns>The value of the expression.</returns> public double CalculateExpression(Dictionary <string, double> operands, Calculator_Expression expression) { var x = expression.Evaluate(operands); if (x is bool) { return((bool)x ? 1.0 : 0.0); } return((double)x); }
Calculator_Expression ParseAtom() { string id; Calculator_Expression x = null; FunctionDefinition fnDef = null; switch (_token.Type) { // literals case TKTYPE.LITERAL: x = new Calculator_Expression(_token); break; // identifiers case TKTYPE.IDENTIFIER: // get identifier id = (string)_token.Value; // look for functions if (_fnTbl.TryGetValue(id, out fnDef)) { var p = GetParameters(); var pCnt = p == null ? 0 : p.Count; if (fnDef.ParmMin != -1 && pCnt < fnDef.ParmMin) { Throw($"Too few parameters for function {id}."); } if (fnDef.ParmMax != -1 && pCnt > fnDef.ParmMax) { Throw($"Too many parameters for function {id}."); } x = new FunctionExpression(fnDef, p); break; } if (!_vars.ContainsKey(id)) { _vars.Add(id, 1); // if variable does not exist, add with value 1 } else { _vars[id]++; // else increase value by 1. At this point, value counts usage. } x = new VariableExpression(_vars, id); break; // sub-expressions case TKTYPE.GROUP: // anything other than opening parenthesis is illegal here if (_token.ID != TKID.OPEN) { Throw("Expression expected."); } // get expression GetToken(); x = ParseOr(); // check that the parenthesis was closed if (_token.ID != TKID.CLOSE) { Throw("Unbalanced parenthesis."); } break; // logical sub-expressions case TKTYPE.LGROUP: if (!isCond) { Throw("Curly brackets are only allowed within condition formulas."); } // anything other than opening parenthesis is illegal here if (_token.ID != TKID.LOPEN) { Throw("Expression expected."); } // get expression GetToken(); x = ParseOr(); // check that the parenthesis was closed if (_token.ID != TKID.LCLOSE) { Throw("Unbalanced parenthesis."); } break; } // make sure we got something... if (x == null) { Throw($"Unexpected token '{_token.Value}' found in position {_ptr} of the formula."); } // done GetToken(); return(x); }
/// <summary> /// Evaluates an expression. /// </summary> /// <param name="expression">Expression to evaluate.</param> /// <returns>The value of the expression.</returns> public double Evaluate(Calculator_Expression expression) { return((double)expression.Evaluate(null)); }
// This function will go through a pre-compiled expression and try to super-optimize it void OptimizeExpression(ref Calculator_Expression exp) { if (exp is BinaryExpression) // optimize BinaryExpressions { Calculator_Expression lft = (exp as BinaryExpression)._lft; Calculator_Expression rgt = (exp as BinaryExpression)._rgt; OptimizeExpression(ref (exp as BinaryExpression)._lft); OptimizeExpression(ref (exp as BinaryExpression)._rgt); // if this is a multiplication try to optimize: // - multiplications by 0 // - multiplications by 1 (e.g. due to #m footnotes) // - multiplications where the order can be changed to bring literals together (e.g. "2 * b * 3" = "2 * 3 * b" = "6 * b") if (exp._token.ID == TKID.MUL) { if (lft._token.Type == TKTYPE.LITERAL) // check if the left side is literal { if ((double)lft._token.Value == 1) // if 1, return the right side { exp = rgt; return; } if ((double)lft._token.Value == 0) // if 0, return 0 { exp = new Calculator_Expression(new Token(0.0, TKID.ATOM, TKTYPE.LITERAL)); return; } if (rgt._token.ID == TKID.MUL) // check if the right side is also a multiplication, maybe we can move literals around { if ((rgt as BinaryExpression)._lft._token.Type == TKTYPE.LITERAL) { (exp as BinaryExpression)._lft._token.Value = (double)lft._token.Value * (double)(rgt as BinaryExpression)._lft._token.Value; (exp as BinaryExpression)._rgt = (rgt as BinaryExpression)._rgt; } if ((rgt as BinaryExpression)._rgt._token.Type == TKTYPE.LITERAL) { (exp as BinaryExpression)._lft._token.Value = (double)lft._token.Value * (double)(rgt as BinaryExpression)._rgt._token.Value; (exp as BinaryExpression)._rgt = (rgt as BinaryExpression)._lft; } } } if (rgt._token.Type == TKTYPE.LITERAL) // check if the right side is literal { if ((double)rgt._token.Value == 1) // if 1, return the right side { exp = lft; return; } if ((double)rgt._token.Value == 0) // if 0, return 0 { exp = new Calculator_Expression(new Token(0.0, TKID.ATOM, TKTYPE.LITERAL)); return; } if (lft._token.ID == TKID.MUL) // check if the left side is also a multiplication, maybe we can move literals around { if ((lft as BinaryExpression)._lft._token.Type == TKTYPE.LITERAL) { (exp as BinaryExpression)._rgt._token.Value = (double)rgt._token.Value * (double)(lft as BinaryExpression)._lft._token.Value; (exp as BinaryExpression)._lft = (lft as BinaryExpression)._rgt; } if ((lft as BinaryExpression)._rgt._token.Type == TKTYPE.LITERAL) { (exp as BinaryExpression)._rgt._token.Value = (double)rgt._token.Value * (double)(lft as BinaryExpression)._rgt._token.Value; (exp as BinaryExpression)._lft = (lft as BinaryExpression)._lft; } } } } } exp.Optimize(); // finally, perform standard optimization again for any remaining calculations }
// helper function for PropagateFootnotes void CopyFootnote(ref Calculator_Expression exp, string foot) { if (exp is VariableExpression) // if variable, then copy footnote { // get the variable name string v = (exp as VariableExpression)._name; // reduce the usage count _vars[v]--; // if no usage left, then remove variable if (_vars[v] == 0) { _vars.Remove(v); } // build new name v += "#" + foot; // add new variable or increase usage count if (_vars.ContainsKey(v)) { _vars[v]++; } else { _vars.Add(v, 1); } // make sure the token is also updated (exp as VariableExpression)._name = v; } else if (exp is BinaryExpression) // else dig deeper { Calculator_Expression lft = (exp as BinaryExpression)._lft; Calculator_Expression rgt = (exp as BinaryExpression)._rgt; if (lft is VariableExpression || lft is BinaryExpression || lft is UnaryExpression || lft is FunctionExpression) { CopyFootnote(ref lft, foot); } if (rgt is VariableExpression || rgt is BinaryExpression || rgt is UnaryExpression || rgt is FunctionExpression) { CopyFootnote(ref rgt, foot); } } else if (exp is UnaryExpression) { Calculator_Expression expr = (exp as UnaryExpression)._expr; if (expr is VariableExpression || expr is BinaryExpression || expr is UnaryExpression || expr is FunctionExpression) { CopyFootnote(ref expr, foot); } } else if (exp is FunctionExpression) { for (int i = 0; i < (exp as FunctionExpression)._parms.Count; i++) { Calculator_Expression expr = (exp as FunctionExpression)._parms[i]; if (expr is VariableExpression || expr is BinaryExpression || expr is UnaryExpression || expr is FunctionExpression) { CopyFootnote(ref expr, foot); } } } else { Throw("Invalid use of footnote."); } }
// ** ctor public BinaryExpression(Token tk, Calculator_Expression exprLeft, Calculator_Expression exprRight) : base(tk) { _lft = exprLeft; _rgt = exprRight; }
// ** ctor public UnaryExpression(Token tk, Calculator_Expression expr) : base(tk) { _expr = expr; }