C# 4 expressions: loops, goto, label, if and even for ! [Part III]

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

Then the code to recognize our methods and transform them to something understandable to the expression API :

 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; 
}

You can notice that most of methods directly have a corresponding one in the expression API. But some of them are higher level ones like ‘While’ or ‘For’ that do not exist in the expression API.

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

We now have richer C# expressions possibilities.

I hope one day we will have natural support for all of this and even more in our favorite language.

Mitsu

The whole project is available here: https://code.msdn.microsoft.com/CSharp4Expressions/