How to convert a String to its equivalent LINQ Expression Tree?

C#LambdaAntlrDslPredicate

C# Problem Overview


This is a simplified version of the original problem.

I have a class called Person:

public class Person {
  public string Name { get; set; }
  public int Age { get; set; }
  public int Weight { get; set; }
  public DateTime FavouriteDay { get; set; }
}

...and lets say an instance:

var bob = new Person {
  Name = "Bob",
  Age = 30,
  Weight = 213,
  FavouriteDay = '1/1/2000'
}

I would like to write the following as a string in my favourite text editor....

(Person.Age > 3 AND Person.Weight > 50) OR Person.Age < 3

I would like to take this string and my object instance and evaluate a TRUE or FALSE - i.e. evaluating a Func<Person, bool> on the object instance.

Here are my current thoughts:

  1. Implement a basic grammar in ANTLR to support basic Comparison and Logical Operators. I am thinking of copying the Visual Basic precedence and some of the featureset here: http://msdn.microsoft.com/en-us/library/fw84t893(VS.80).aspx
  2. Have ANTLR create a suitable AST from a provided string.
  3. Walk the AST and use the Predicate Builder framework to dynamically create the Func<Person, bool>
  4. Evaluate the predicate against an instance of Person as required

My question is have I totally overbaked this? any alternatives?


EDIT: Chosen Solution

I decided to use the Dynamic Linq Library, specifically the Dynamic Query class provided in the LINQSamples.

Code below:

using System;
using System.Linq.Expressions;
using System.Linq.Dynamic;

namespace ExpressionParser
{
  class Program
  {
    public class Person
    {
      public string Name { get; set; }
      public int Age { get; set; }
      public int Weight { get; set; }
      public DateTime FavouriteDay { get; set; }
    }

    static void Main()
    {
      const string exp = @"(Person.Age > 3 AND Person.Weight > 50) OR Person.Age < 3";
      var p = Expression.Parameter(typeof(Person), "Person");
      var e = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] { p }, null, exp);
      var bob = new Person
      {
        Name = "Bob",
        Age = 30,
        Weight = 213,
        FavouriteDay = new DateTime(2000,1,1)
      };

      var result = e.Compile().DynamicInvoke(bob);
      Console.WriteLine(result);
      Console.ReadKey();
    }
  }
}

Result is of type System.Boolean, and in this instance is TRUE.

Many thanks to Marc Gravell.

Include System.Linq.Dynamic nuget package, documentation here

C# Solutions


Solution 1 - C#

Would the dynamic linq library help here? In particular, I'm thinking as a Where clause. If necessary, put it inside a list/array just to call .Where(string) on it! i.e.

var people = new List<Person> { person };
int match = people.Where(filter).Any();

If not, writing a parser (using Expression under the hood) isn't hugely taxing - I wrote one similar (although I don't think I have the source) in my train commute just before xmas...

Solution 2 - C#

Another such library is Flee

I did a quick comparison of Dynamic Linq Library and Flee and Flee was 10 times faster for the expression "(Name == \"Johan\" AND Salary > 500) OR (Name != \"Johan\" AND Salary > 300)"

This how you can write your code using Flee.

static void Main(string[] args)
{
  var context = new ExpressionContext();
  const string exp = @"(Person.Age > 3 AND Person.Weight > 50) OR Person.Age < 3";
  context.Variables.DefineVariable("Person", typeof(Person));
  var e = context.CompileDynamic(exp);

  var bob = new Person
  {
    Name = "Bob",
    Age = 30,
    Weight = 213,
    FavouriteDay = new DateTime(2000, 1, 1)
  };

  context.Variables["Person"] = bob;
  var result = e.Evaluate();
  Console.WriteLine(result);
  Console.ReadKey();
}

Solution 3 - C#

void Main()
{
	var testdata = new List<Ownr> {
		//new Ownr{Name = "abc", Qty = 20}, // uncomment this to see it getting filtered out
		new Ownr{Name = "abc", Qty = 2},
		new Ownr{Name = "abcd", Qty = 11},
		new Ownr{Name = "xyz", Qty = 40},
		new Ownr{Name = "ok", Qty = 5},
	};

	Expression<Func<Ownr, bool>> func = Extentions.strToFunc<Ownr>("Qty", "<=", "10");
	func = Extentions.strToFunc<Ownr>("Name", "==", "abc", func);

	var result = testdata.Where(func.ExpressionToFunc()).ToList();

	result.Dump();
}

public class Ownr
{
	public string Name { get; set; }
	public int Qty { get; set; }
}

public static class Extentions
{
	public static Expression<Func<T, bool>> strToFunc<T>(string propName, string opr, string value, Expression<Func<T, bool>> expr = null)
	{
		Expression<Func<T, bool>> func = null;
		try
		{
			var type = typeof(T);
			var prop = type.GetProperty(propName);
			ParameterExpression tpe = Expression.Parameter(typeof(T));
			Expression left = Expression.Property(tpe, prop);
			Expression right = Expression.Convert(ToExprConstant(prop, value), prop.PropertyType);
			Expression<Func<T, bool>> innerExpr = Expression.Lambda<Func<T, bool>>(ApplyFilter(opr, left, right), tpe);
			if (expr != null)
				innerExpr = innerExpr.And(expr);
			func = innerExpr;
		}
		catch (Exception ex)
		{
			ex.Dump();
		}

		return func;
	}
	private static Expression ToExprConstant(PropertyInfo prop, string value)
	{
		object val = null;

		try
		{
			switch (prop.Name)
			{
				case "System.Guid":
					val = Guid.NewGuid();
					break;
				default:
					{
						val = Convert.ChangeType(value, prop.PropertyType);
						break;
					}
			}
		}
		catch (Exception ex)
		{
			ex.Dump();
		}

		return Expression.Constant(val);
	}
	private static BinaryExpression ApplyFilter(string opr, Expression left, Expression right)
	{
		BinaryExpression InnerLambda = null;
		switch (opr)
		{
			case "==":
			case "=":
				InnerLambda = Expression.Equal(left, right);
				break;
			case "<":
				InnerLambda = Expression.LessThan(left, right);
				break;
			case ">":
				InnerLambda = Expression.GreaterThan(left, right);
				break;
			case ">=":
				InnerLambda = Expression.GreaterThanOrEqual(left, right);
				break;
			case "<=":
				InnerLambda = Expression.LessThanOrEqual(left, right);
				break;
			case "!=":
				InnerLambda = Expression.NotEqual(left, right);
				break;
			case "&&":
				InnerLambda = Expression.And(left, right);
				break;
			case "||":
				InnerLambda = Expression.Or(left, right);
				break;
		}
		return InnerLambda;
	}

	public static Expression<Func<T, TResult>> And<T, TResult>(this Expression<Func<T, TResult>> expr1, Expression<Func<T, TResult>> expr2)
	{
		var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
		return Expression.Lambda<Func<T, TResult>>(Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters);
	}

	public static Func<T, TResult> ExpressionToFunc<T, TResult>(this Expression<Func<T, TResult>> expr)
	{
		var res = expr.Compile();
		return res;
	}
}

LinqPad has the Dump() method

Solution 4 - C#

You might take a look at the DLR. It allows you to evaluate and execute scripts inside .NET 2.0 application. Here's a sample with IronRuby:

using System;
using IronRuby;
using IronRuby.Runtime;
using Microsoft.Scripting.Hosting;

class App
{
    static void Main()
    {
        var setup = new ScriptRuntimeSetup();
        setup.LanguageSetups.Add(
            new LanguageSetup(
                typeof(RubyContext).AssemblyQualifiedName,
                "IronRuby",
                new[] { "IronRuby" },
                new[] { ".rb" }
            )
        );
        var runtime = new ScriptRuntime(setup);
        var engine = runtime.GetEngine("IronRuby");
        var ec = Ruby.GetExecutionContext(runtime);
        ec.DefineGlobalVariable("bob", new Person
        {
            Name = "Bob",
            Age = 30,
            Weight = 213,
            FavouriteDay = "1/1/2000"
        });
        var eval = engine.Execute<bool>(
            "return ($bob.Age > 3 && $bob.Weight > 50) || $bob.Age < 3"
        );
        Console.WriteLine(eval);

    }
}

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public int Weight { get; set; }
    public string FavouriteDay { get; set; }
}

Of course this technique is based on runtime evaluation and code cannot be verified at compile time.

Solution 5 - C#

Here is an example of a Scala DSL based parser combinator for parsing and evaluating of arithmetic expressions.

import scala.util.parsing.combinator._
/** 
* @author Nicolae Caralicea
* @version 1.0, 04/01/2013
*/
class Arithm extends JavaTokenParsers {
  def expr: Parser[List[String]] = term ~ rep(addTerm | minusTerm) ^^
    { case termValue ~ repValue => termValue ::: repValue.flatten }

  def addTerm: Parser[List[String]] = "+" ~ term ^^
    { case "+" ~ termValue => termValue ::: List("+") }

  def minusTerm: Parser[List[String]] = "-" ~ term ^^
    { case "-" ~ termValue => termValue ::: List("-") }

  def term: Parser[List[String]] = factor ~ rep(multiplyFactor | divideFactor) ^^
    {
      case factorValue1 ~ repfactor => factorValue1 ::: repfactor.flatten
    }

  def multiplyFactor: Parser[List[String]] = "*" ~ factor ^^
    { case "*" ~ factorValue => factorValue ::: List("*") }

  def divideFactor: Parser[List[String]] = "/" ~ factor ^^
    { case "/" ~ factorValue => factorValue ::: List("/") }

  def factor: Parser[List[String]] = floatingPointConstant | parantExpr

  def floatingPointConstant: Parser[List[String]] = floatingPointNumber ^^
    {
      case value => List[String](value)
    }

  def parantExpr: Parser[List[String]] = "(" ~ expr ~ ")" ^^
    {
      case "(" ~ exprValue ~ ")" => exprValue
    }

  def evaluateExpr(expression: String): Double = {
    val parseRes = parseAll(expr, expression)
    if (parseRes.successful) evaluatePostfix(parseRes.get)
    else throw new RuntimeException(parseRes.toString())
  }
  private def evaluatePostfix(postfixExpressionList: List[String]): Double = {
    import scala.collection.immutable.Stack

    def multiply(a: Double, b: Double) = a * b
    def divide(a: Double, b: Double) = a / b
    def add(a: Double, b: Double) = a + b
    def subtract(a: Double, b: Double) = a - b

    def executeOpOnStack(stack: Stack[Any], operation: (Double, Double) => Double): (Stack[Any], Double) = {
      val el1 = stack.top
      val updatedStack1 = stack.pop
      val el2 = updatedStack1.top
      val updatedStack2 = updatedStack1.pop
      val value = operation(el2.toString.toDouble, el1.toString.toDouble)
      (updatedStack2.push(operation(el2.toString.toDouble, el1.toString.toDouble)), value)
    }
    val initial: (Stack[Any], Double) = (Stack(), null.asInstanceOf[Double])
    val res = postfixExpressionList.foldLeft(initial)((computed, item) =>
      item match {
        case "*" => executeOpOnStack(computed._1, multiply)
        case "/" => executeOpOnStack(computed._1, divide)
        case "+" => executeOpOnStack(computed._1, add)
        case "-" => executeOpOnStack(computed._1, subtract)
        case other => (computed._1.push(other), computed._2)
      })
    res._2
  }
}

object TestArithmDSL {
  def main(args: Array[String]): Unit = {
    val arithm = new Arithm
    val actual = arithm.evaluateExpr("(12 + 4 * 6) * ((2 + 3 * ( 4 + 2 ) ) * ( 5 + 12 ))")
    val expected: Double = (12 + 4 * 6) * ((2 + 3 * ( 4 + 2 ) ) * ( 5 + 12 ))
    assert(actual == expected)
  }
}

The equivalent expression tree or parse tree of the provided arithmetic expression would be of the Parser[List[String]] type.

More details are at the following link:

http://nicolaecaralicea.blogspot.ca/2013/04/scala-dsl-for-parsing-and-evaluating-of.html

Solution 6 - C#

In addition to Dynamic Linq Library (which builds strongly typed expression and requires strongly typed variables) I recommend better alternative: linq parser that part of NReco Commons Library (open source). It aligns all types and performs all invocations at runtime and behaves like dynamic language:

var lambdaParser = new NReco.LambdaParser();
var varContext = new Dictionary<string,object>();
varContext["one"] = 1M;
varContext["two"] = "2";
 
Console.WriteLine( lambdaParser.Eval("two>one && 0<one ? (1+8)/3+1*two : 0", varContext) ); // --> 5

Solution 7 - C#

Although this is relatively old post - this is the code for expression builder:AnyService - ExpressionTreeBuilder These are the unit tests:AnyService - ExpressionTreeBuilder Unit Tests

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionCodebrainView Question on Stackoverflow
Solution 1 - C#Marc GravellView Answer on Stackoverflow
Solution 2 - C#chikakView Answer on Stackoverflow
Solution 3 - C#suneelsarrafView Answer on Stackoverflow
Solution 4 - C#Darin DimitrovView Answer on Stackoverflow
Solution 5 - C#ncaraliceaView Answer on Stackoverflow
Solution 6 - C#Vitaliy FedorchenkoView Answer on Stackoverflow
Solution 7 - C#Roi ShabtaiView Answer on Stackoverflow