Below is a source code to the expression parser with ability to parse combined expressions, that is numeric and booleans, and variable support. See Main method for demo purposes.
Code Snippet
- using System;
- using System.Collections.Generic;
- public class ExpressionEvaluator
- {
- #region class AtomicExpression
- /// <summary>
- /// This class holds atomic expressions. It holds either variables, values or operators.
- /// If the class holds operator, it can provide result given the input values
- /// </summary>
- public class AtomicExpression
- {
- private string _value;
- /// <summary>
- /// Parsed value of the strig value used during evaluation
- /// </summary>
- public object ParsedValue { get; set; }
- /// <summary>
- /// Value of the current expression
- /// It is either numeric value or name of the variable (e.g. 1, A, Aleph)
- /// </summary>
- public string Value
- {
- get
- {
- return _value;
- }
- set
- {
- double outd;
- if (double.TryParse(value, out outd)) {
- ParsedValue = outd;
- }
- bool outb;
- if (bool.TryParse(value, out outb)) {
- ParsedValue = outb;
- }
- _value = value;
- }
- }
- /// <summary>
- /// String representation of the operator (e.g. '==' or '||')
- /// </summary>
- public string Operator { get; set; }
- /// <summary>
- /// Evaluates the unary operator with provided value
- /// </summary>
- /// <param name="value">Value for unary operator</param>
- /// <returns>Evaluated expression of the unary operator</returns>
- public object Evaluate(object value)
- {
- if (Operator == "!") {
- return !((bool)value);
- }
- throw new NotImplementedException(Operator + " operator not implemented");
- }
- /// <summary>
- /// Evaluates the binary operator expression with provided values
- /// </summary>
- /// <param name="val1">Left-side value for the operator</param>
- /// <param name="val2">Right-side value for the operator</param>
- /// <returns>Output value of the binary expression</returns>
- public object Evaluate(object val1, object val2)
- {
- Console.Write("{0} {1} {2} = ", val1, Operator, val2);
- switch (Operator) {
- case "+":
- return (double)val1 + (double)val2;
- case "-":
- return (double)val1 - (double)val2;
- case "*":
- return (double)val1 * (double)val2;
- case "/":
- return (double)val1 / (double)val2;
- case ">":
- return (double)val1 > (double)val2;
- case ">=":
- return (double)val1 >= (double)val2;
- case "<":
- return (double)val1 < (double)val2;
- case "<=":
- return (double)val1 <= (double)val2;
- case "==":
- return (double)val1 == (double)val2;
- case "!=":
- return (double)val1 != (double)val2;
- case "&&":
- return (bool)val1 && (bool)val2;
- case "||":
- return (bool)val1 || (bool)val2;
- }
- throw new NotImplementedException(Operator + " operator not implemented");
- }
- public override string ToString()
- {
- return string.Format("{0}{1}", Value, Operator);
- }
- }
- #endregion
- #region Evaluate(List<AtomicExpression> list, Dictionary<string, object> variables)
- /// <summary>
- /// Evaluates the input AtomicExpression (see AtomicExpression) list with the list of provided variables
- /// and calculates the numeric or boolean output. List of expressions is sorted in infix order.
- /// </summary>
- /// <param name="list">List of input expressions. Expressions are evaluated in the postfix manner.</param>
- /// <param name="variables">List of variables and their values used during the evaluation</param>
- /// <returns></returns>
- public object Evaluate(List<AtomicExpression> list, Dictionary<string, object> variables)
- {
- var stack = new Stack<object>();
- foreach (var expression in list) {
- // double value
- if (expression.ParsedValue != null) {
- stack.Push(expression.ParsedValue);
- continue;
- }
- // variable
- if (!string.IsNullOrEmpty(expression.Value)) {
- stack.Push(variables[expression.Value]);
- continue;
- }
- // unary operator
- object result;
- if (expression.Operator == "!") {
- result = expression.Evaluate(stack.Pop());
- Console.WriteLine(result);
- }
- // binary operator
- else {
- var p2 = stack.Pop();
- var p1 = stack.Pop();
- result = expression.Evaluate(p1, p2);
- Console.WriteLine(result);
- }
- stack.Push(result);
- }
- Console.WriteLine("Result: " + stack.Peek());
- return stack.Pop();
- }
- #endregion
- #region CheckParenthesis(string expression)
- /// <summary>
- /// Checks if given expression is well formed with parenthesis
- /// </summary>
- /// <param name="expression">Input expression (e.g. A+B-(2*2)>0)</param>
- /// <returns></returns>
- public virtual bool CheckParenthesis(string expression)
- {
- var stack = new Stack<string>();
- foreach (var c in expression) {
- switch (c) {
- case '(':
- stack.Push(c.ToString());
- break;
- case ')':
- if (stack.Count == 0) {
- return false;
- }
- stack.Pop();
- break;
- }
- }
- return stack.Count <= 0;
- }
- #endregion
- #region List<AtomicExpression> Postfix(string expression)
- /// <summary>
- /// Converts the input infix expression into the postfix and creates the list
- /// of atomic expressions in this infix order
- /// </summary>
- /// <param name="expression">Infix expression (e.g. A+B-(2*2)>0||B)</param>
- /// <returns>List of atomic expressions sorted in the infix order</returns>
- public virtual List<AtomicExpression> Postfix(string expression)
- {
- var expressions = new List<AtomicExpression>();
- var variable = string.Empty;
- var stack = new Stack<string>();
- // remove spaces
- expression = expression.Replace(" ", string.Empty);
- char c;
- string sc;
- for (var i = 0; i < expression.Length; i++) {
- c = expression[i];
- // variable or number
- if (char.IsLetterOrDigit(c)) {
- variable += c;
- }
- // left parthesis
- else if (c == '(') {
- stack.Push(c.ToString());
- }
- // right parenthesis
- else if (c == ')') {
- // add variable
- if (!string.IsNullOrEmpty(variable)) {
- expressions.Add(new AtomicExpression { Value = variable });
- }
- variable = string.Empty;
- while (!stack.Peek().Equals("(")) {
- expressions.Add(new AtomicExpression { Operator = stack.Pop() });
- }
- stack.Pop();
- }
- // operator
- else {
- sc = c.ToString();
- // add variable
- if (!string.IsNullOrEmpty(variable)) {
- expressions.Add(new AtomicExpression { Value = variable });
- }
- variable = string.Empty;
- // check for operators having 2 characters
- if (i < expression.Length - 1 && !char.IsLetterOrDigit(expression[i + 1]) && expression[i + 1] != '(' && expression[i + 1] != ')') {
- sc += expression[i + 1];
- i++;
- }
- // add all expressions with higher or same priority
- while (stack.Count > 0 && Priority(stack.Peek()) >= Priority(sc)) {
- expressions.Add(new AtomicExpression { Operator = stack.Pop() });
- }
- stack.Push(sc);
- }
- }
- while (stack.Count > 0) {
- expressions.Add(new AtomicExpression { Operator = stack.Pop() });
- }
- return expressions;
- }
- #endregion
- // main method
- #region Main(string[] args)
- public static void Main(string[] args)
- {
- ExpressionEvaluator s = new ExpressionEvaluator();
- var list = s.Postfix("A && !(B > 2 + 1) || ( !A && !(B==2))");
- Console.WriteLine("------------------------------------------");
- foreach (var expression in list) {
- Console.Write(expression.ToString());
- }
- Console.WriteLine("\r\n------------------------------------------");
- var dict = new Dictionary<string, object>();
- dict.Add("A", true);
- dict.Add("B", 3d);
- s.Evaluate(list, dict);
- Console.ReadKey();
- }
- #endregion
- // private methods
- #region Priority(string x)
- private static int Priority(string x)
- {
- if (x.Equals("||"))
- return 0;
- if (x.Equals("&&"))
- return 1;
- if (x[0].Equals('<') || x[0].Equals('>') || x[0].Equals('=') || x[0].Equals('!'))
- return 2;
- if (x.Equals("+") || x.Equals("-"))
- return 3;
- if (x.Equals("*") || x.Equals("/"))
- return 4;
- if (x.Equals("(") || x.Equals(")"))
- return -1;
- throw new NotImplementedException(x + " is not implemented");
- }
- #endregion
- }
2 comments:
use just "A" as expresion string - it woun't work
also double not (!!A)
also negation (-A)
also you cant compare two boolean (A>B == C)
but anyway thanks, i'm using it with those bugs fixed and some functonality stripped out
Any chance you can contribute back the code? I am working on making it handle variable names longer than one character and do string comparisons as well. Thanks.
Post a Comment