views:

2294

answers:

3

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 = 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.

+1  A: 

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.

Darin Dimitrov
I want to be able to protect against execution of 'bad code'... would this be a good fit?
Codebrain
What do you mean by 'bad code'? Someone typing an expression that is not valid? In this case you will get a runtime exception when trying to evaluate the script.
Darin Dimitrov
@darin, things like starting processes, changing data, etc.
Simon Svensson
'bad code' = something that isn't an expression of type Func<Person, bool> (e.g. deleting files from a disk, spinning up a process etc...)
Codebrain
+4  A: 

Would the dynamic linq library help here? In particular, I'm thinking as a Where clause. If necessary, put it isn't 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...

Marc Gravell
+2  A: 

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();
        }
chikak