using ExpandoObject to call methods in AST

Jan 8, 2010 at 1:10 AM

I'll try and be succinct. Here is a snippet of our language. 2 things to note: recursion and calling methods before they are defined.

function foo {
if(a > 10)
bar()
else
foo()

//do something here
}

function bar {
//do something
}

Based on previous feed back I tried to get the following code to run in an attempt to get something like the above working:

dynamic eoo = new ExpandoObject();
eoo.Func2 = new Action(delegate { eoo.Func1(); });
eoo.Func1 = new Action(delegate { Trace.WriteLine("Func1 called"); });

eoo.Func1();
eoo.Func2();


ParameterExpression p = Expression.Variable(typeof(ExpandoObject));

Expression assign = Expression.Assign(
p,
Expression.Constant(eoo));

BlockExpression be = Expression.Block(
new ParameterExpression[] { p },
assign,
Expression.Call(p, "Func1", null, null)
);

Expression.Lambda(be).Compile().DynamicInvoke();

 

An output of "Func1 called" printed out 3 times would be what I want.

The output of this method is

Func1 called
Func1 called
...<Exception>... No method 'Func1' exists on type 'System.Dynamic.ExpandoObject'.
    System.InvalidOperationException: No method 'Func1' exists on type 'System.Dynamic.ExpandoObject'.

 

I imagine I need to use Expression.Dynamic, but I'm not sure how. Also, I messed around with creating my own DynamiObject instead of using the ExpandoObject, then overriding the TrySetMember, obtaining the SetMemberBinder, which inherits from CallSiteBinder and passing that to Exception.Dynamic. Every attempt was met with failure.

 

Any suggestions on how to get this to work? I think if I can get this to work I can take it from there and incorporate it into our language.

 

 

Jan 8, 2010 at 6:08 PM

This is for those of you that might be working on a similar problem.

After more trial and error and some help from other posts on here I was able to come up with a solution to enable our language to support this:

int a

function foo {
if(a>=1) {
print a
a--
bar()
foo()
}
}

function bar {
print 10
}

 

2 things of interest here: recursion and calling methods before they are defined.

Here is the code:

 

public class TestClass
{
    public void RunRecursion()
    {
        ParameterExpression input = Expression.Parameter(typeof(long));

        var test = Expression.GreaterThanOrEqual(input, Expression.Constant(1L));

        var aMethod = Expression.Parameter(typeof(Action));
        var otherMethod = Expression.Parameter(typeof(Action));

        Expression body = Expression.Block(
            Expression.IfThen(
                test,
                Expression.Block(
                    Expression.Call(
                        typeof(TestClass).GetMethod("PrintLong"),
                        Microsoft.Scripting.Ast.Utils.Convert(input, typeof(long))
                    ),
                    Expression.SubtractAssign(input, Expression.Constant(1L)),
                    Expression.Invoke(otherMethod),
                    Expression.Invoke(aMethod)                        
                )
            )
        );

        var methodBody = Expression.Lambda(body);

        var otherMethodBody = Expression.Lambda(Expression.Block(
            Expression.Call(
                typeof(TestClass).GetMethod("PrintLong"),
                Expression.Constant(10L)
            )
        ));

        var block = Expression.Block(
            new ParameterExpression[] { aMethod, input, otherMethod },
            Expression.Assign(input, Expression.Constant(5L)),
            Expression.Assign(aMethod, methodBody),
            Expression.Assign(otherMethod, otherMethodBody),
            Expression.Invoke(aMethod)
        );

        Expression.Lambda(block).Compile().DynamicInvoke();
    }

    public static void PrintLong(long val)
    {
        Trace.WriteLine(val);
    }
}

The output:

5
10
4
10
3
10
2
10
1
10

 

Hope that helps someone trying to do something similar. Feel free to chime in if you have suggestions on how to make this better.

 

 

Jan 8, 2010 at 7:51 PM

> I imagine I need to use Expression.Dynamic, but I'm not sure how. Also, I messed around with creating my own DynamiObject instead of using the ExpandoObject, then overriding the TrySetMember, obtaining the SetMemberBinder, which inherits from CallSiteBinder and passing that to Exception.Dynamic. Every attempt was met with failure.

Yup, you want to use Expression.Dynamic. The trick is defining the SetMemberBinder. The way DLR works is first it will ask the object to SetMember, and if that doesn’t work it asks your language’s SetMemberBinder what to do. That’s where you put your .NET binding logic.

A quick way to get something working: you could try using the C# SetMemberBinder. It’s pretty easy to look at what C# does with the “eoo.Func1” line with something like Reflector, I’m guessing the binder creation looks something like “Microsoft.CSharp.RuntimeBinder.Binder.SetMember(…, “Func1”, … )”. If you passed a binder created like that to Expression.Dynamic it should give you C# binding. (I’m surprised your trick with DynamicObject didn’t work… it sounds like a clever way of getting a hold of C#’s SetMemberBinder… maybe there’s an issue here I’m not thinking of)

Better option long term is to use the default .NET binder that lives in Microsoft.Scripting.dll. I’m not up with the current state of it but it has quite full featured .NET interop and supports lots of customization.

- John

Jan 8, 2010 at 7:52 PM

Oops, I don’t know why I was thinking SetMember =) … replace with InvokeMember and it should all still apply!

- John