We now have block and variable support in our very small meta language. Let’s try to use this basic engine to add higher functionalities.
.Net 4.0 expression API also brings new instructions such as Loop, Goto, Label, IfThenElse, etc.
We will add them with always the same process: adding a comprehensive method in the Block class and implement the appropriate transformation in the visitor.
First, here is the Block class with all our meta language dictionary. I have never written so many methods without implementing them ! :)
public class Block { //prevents the use of the default public //constructor //so we hare sure that the only way to get //Block instances is to use Block.Start() private Block() { } public static Block Start<T>(T locals, Action<T> block) { throw new NotImplementedException(); } public static R Start<T, R>(T locals, Func<T, R> block) { throw new NotImplementedException(); } public static Block Default { get; private set; } public Block _(Action action) { throw new NotImplementedException(); } public Block Assign<T>(Func<T> leftExpression, T rightExpression) { throw new NotImplementedException(); } public Block Loop(Action block) { throw new NotImplementedException(); } public Block IfThen(bool testExpression, Action thenBlock) { throw new NotImplementedException(); } public Block IfThenElse(bool testExpression, Action thenBlock, Action elseBlock) { throw new NotImplementedException(); } public Block While(bool testExpression, Action block) { throw new NotImplementedException(); } public Block For(int from, Predicate<int> condition, Action<int> block) { throw new NotImplementedException(); } public Block Label(string name) { throw new NotImplementedException(); } public Block Goto(string name) { throw new NotImplementedException(); } }
private Expression VisitBlockMethodCall(MethodCallExpression node) { if (IsMethodOf<Block>(node, "Label")) { string labelName = (string)(node.Arguments[0] as ConstantExpression).Value; return Expression.Label( GetLabelTarget(labelName)); } if (IsMethodOf<Block>(node, "IfThen")) return Expression.IfThen( Visit(node.Arguments[0]), Visit((node.Arguments[1] as LambdaExpression).Body)); if (IsMethodOf<Block>(node, "IfThenElse")) return Expression.IfThenElse( Visit(node.Arguments[0]), Visit((node.Arguments[1] as LambdaExpression).Body), Visit((node.Arguments[2] as LambdaExpression).Body)); if (IsMethodOf<Block>(node, "Goto")) { string labelName = (string)(node.Arguments[0] as ConstantExpression).Value; return Expression.Goto( GetLabelTarget(labelName)); } if (IsMethodOf<Block>(node, "Loop")) { var result = (node.Arguments[0] as LambdaExpression).Body; return Expression.Loop(Visit(result)); } if (IsMethodOf<Block>(node, "While")) { var exitLabel = Expression.Label(); var block = Expression.Block( new Expression[] { Expression.Loop( Expression.Block( new Expression[] { Expression.IfThen( Expression.Not( Visit(node.Arguments[0])), Expression.Goto(exitLabel)), Visit((node.Arguments[1] as LambdaExpression).Body) })), Expression.Label(exitLabel) }); return block; } if (IsMethodOf<Block>(node, "For")) { var l = node.Arguments[2] as LambdaExpression; var i = l.Parameters[0]; var test = node.Arguments[1] as LambdaExpression; var exitLabel = Expression.Label(); var block = Expression.Block( new ParameterExpression[] { i }, new Expression[] { Expression.Assign(i, Visit(node.Arguments[0])), Expression.Loop( Expression.Block( new Expression[] { Expression.IfThen( VisitWithReplaceParameter( test.Parameters[0], i, Expression.Not(test.Body)), Expression.Goto(exitLabel)), VisitWithReplaceParameter( l.Parameters[0], i, l.Body), Expression.Assign(i, Expression.Add(i, Expression.Constant(1))) })), Expression.Label(exitLabel) }); return block; } return null; }
So I had to implement a bigger transformation logic, using many functions of the expression API.
I can now write richer expressions mixing all those features:
Expression<Action> expLabel = () => Block.Default ._(() => Console.WriteLine("Start")) .Goto("exit") ._(() => Console.WriteLine("not printed")) .Label("exit") ._(() => Console.WriteLine("end")); expLabel = ExpressionHelper.Translate(expLabel); expLabel.Compile()(); Console.WriteLine("\nIfThenElse support"); Expression<Action> expIfThenElse = () => Block.Start(new { s = "" }, b => Block.Default ._(() => Console.Write("Enter 'OK'")) .Assign(() => b.s, Console.ReadLine()) .IfThenElse(b.s == "OK", () => Console.WriteLine( "Good for me"), () => Console.WriteLine("Sorry")) ); expIfThenElse = ExpressionHelper.Translate(expIfThenElse); expIfThenElse.Compile()(); Console.WriteLine("\nLoop support"); Expression<Action> expLoop = () => Block.Start(new { i = 0 }, b => Block.Default .Loop(() => Block.Default ._(() => Console.WriteLine("loop: " + b.i)) .Assign(() => b.i, b.i + 1) .IfThen(b.i >= 4, () => Block.Default.Goto("break")) ) .Label("break") ._(() => Console.WriteLine("end")) ); expLoop = ExpressionHelper.Translate(expLoop); expLoop.Compile()(); Console.WriteLine("\nFor support"); Expression<Action> expFor = () => Block.Default .For(0, i => i < 4, i => Console.WriteLine("For:" + i)); expFor = ExpressionHelper.Translate(expFor); expFor.Compile()(); Console.WriteLine("\nWhile support"); Expression<Action> expWhile = () => Block.Start(new { i = 0 }, b => Block.Default .While(b.i < 4, () => Block.Default ._(() => Console.WriteLine("While:" + b.i)) .Assign(() => b.i, b.i + 1)) ._(() => Console.WriteLine("end")) ); expWhile = ExpressionHelper.Translate(expWhile); expWhile.Compile()();
Mitsu
The whole project is available here: http://code.msdn.microsoft.com/CSharp4Expressions/