Creating an expression compiler

Oct 31, 2009 at 9:00 AM
Edited Oct 31, 2009 at 9:06 AM

Wow, I just wanted to say how awesome Irony is!! I spent less than an hour and I already have a working prototype of an expression compiler that creates a LINQ expression tree out of the Irony parse tree.

Here's the rough code:

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Windows.Controls;
using Irony.Parsing;
using Irony.Samples;

namespace Irony.Silverlight.Test
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            Init();
        }

        private void Init()
        {
            Grammar g = new ExpressionEvaluatorGrammar();
            Parser p = new Parser(g);
            ParseTree ast = p.Parse("2 * x + 1");

            ExpressionTreeBuilder builder = new ExpressionTreeBuilder();
            Expression<Func<double, double>> expression = builder.CreateFunction(ast.Root);
            Func<double, double> function = expression.Compile();
            var result = function(4);
        }
    }

    public class ExpressionTreeBuilder
    {
        public ExpressionTreeBuilder()
        {
            Binder = new Binder();
        }

        public Binder Binder { get; set; }

        public Expression<Func<double, double>> CreateFunction(ParseTreeNode root)
        {
            ParameterExpression parameter = Expression.Parameter(typeof(double), "x");
            Binder.RegisterParameter(parameter);
            Expression body = CreateExpression(root);
            var result = Expression.Lambda<Func<double, double>>(body, parameter);
            return result;
        }

        Expression CreateExpression(ParseTreeNode root)
        {
            if (root.Term.Name == "BinExpr")
            {
                return CreateBinaryExpression(root);
            }

            if (root.Term.Name == "identifier")
            {
                return Binder.Resolve(root.Token.Text);
            }

            if (root.Term.Name == "number")
            {
                return CreateLiteralExpression(Convert.ToDouble(root.Token.Value));
            }

            return null;
        }

        Expression CreateLiteralExpression(double arg)
        {
            return Expression.Constant(arg);
        }

        Expression CreateBinaryExpression(ParseTreeNode node)
        {
            Expression left = CreateExpression(node.ChildNodes[0]);
            Expression right = CreateExpression(node.ChildNodes[2]);

            switch (node.ChildNodes[1].Term.Name)
            {
                case "+":
                    return Expression.Add(left, right);
                case "-":
                    return Expression.Subtract(left, right);
                case "*":
                    return Expression.Multiply(left, right);
                case "/":
                    return Expression.Divide(left, right);
                case "^":
                    return Expression.Power(left, right);
            }
            return null;
        }
    }

    public class Binder
    {
        public void RegisterParameter(ParameterExpression parameter)
        {
            parameters.Add(parameter.Name, parameter);
        }

        ParameterExpression ResolveParameter(string parameterName)
        {
            ParameterExpression parameter;
            if (parameters.TryGetValue(parameterName, out parameter))
            {
                return parameter;
            }
            return null;
        }

        Dictionary<string, ParameterExpression> parameters = new Dictionary<string, ParameterExpression>();

        public Expression Resolve(string identifier)
        {
            var parameter = ResolveParameter(identifier);
            if (parameter != null)
            {
                return parameter;
            }

            return null;
        }
    }
}