Note: The DLR is still a moving target :) – Recent changes to the ScriptCode class may make this post irrelevant…

If you’ve spent much time with the Dynamic Language Runtime you’ve maybe encountered the ScriptCode class and the DlrMainCallTarget delegate.  ScriptCode is intended to be the main entry point for your code; it wires up everything the DLR needs (code context, language and scope parameters) and conveniently caches a delegate.

However if you only need the DLR to evaluate arbitrary parameterized expressions, ScriptCode can be a little restrictive.  The classes below are useful alternatives:

Invoker:

public class Invoker
{
    public delegate object CallTarget( Scope scope, LanguageContext language, CodeContext context );

    private static DlrParameterResolver resolver = new DlrParameterResolver();

    protected LanguageContext language;
    private Expression code;
    private Delegate target;

    /// <summary>
    /// Initializes a new instance of the <see cref="Invoker&lt;T&gt;"/> class.
    /// </summary>
    /// <param name="code">The code.</param>
    /// <param name="language">The language.</param>
    public Invoker( Expression code, LanguageContext language )
    {
        this.code = code;
        this.language = language;
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="Invoker"/> class.
    /// </summary>
    /// <param name="invoker">The invoker.</param>
    /// <param name="arguments">The arguments.</param>
    /// <remarks>This constructor "bakes" arguments into the invocation of another <see cref="Invoker"/></remarks>
    public Invoker( Invoker invoker, params object[] arguments )
        : this( invoker, (IEnumerable<object>)arguments )
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="Invoker"/> class.
    /// </summary>
    /// <param name="invoker">The invoker.</param>
    /// <param name="arguments">The arguments.</param>
    /// <remarks>This constructor "bakes" arguments into the invocation of another <see cref="Invoker"/></remarks>
    public Invoker( Invoker invoker, IEnumerable<object> arguments )
        : this( invoker.Target, invoker.language, arguments )
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="Invoker"/> class.
    /// </summary>
    /// <param name="target">The target delegate.</param>
    /// <param name="language">The language context.</param>
    /// <param name="arguments">The arguments.</param>
    /// <remarks>This constructor "bakes" arguments into the invocation of another <see cref="Invoker"/></remarks>
    public Invoker( Delegate target, LanguageContext language, IEnumerable<object> arguments )
    {
        if ( target != null )
        {
            var args = new Expression[]
                {
                    DlrParameterResolver.ScopeParameter,
                    DlrParameterResolver.LanguageParameter,
                    DlrParameterResolver.ContextParameter
                }.Union( arguments.Select(
                    a => ( a is Expression )
                        ? (Expression)a
                        : Expression.Constant( a, typeof( object ) ) ) ).ToArray();

            this.code = Expression.Invoke(
                Expression.Constant( target ),
                args );

            this.language = language;
        }
    }

    /// <summary>
    /// Gets or sets the code.
    /// </summary>
    /// <value>The code.</value>
    public Expression Code
    {
        get { return code; }
        set
        {
            Interlocked.Exchange<Expression>( ref code, value );
            Interlocked.Exchange<Delegate>( ref target, null );
        }
    }

    /// <summary>
    /// Gets the target.
    /// </summary>
    /// <value>The target.</value>
    public Delegate Target
    {
        get
        {
            if ( target == null && code != null )
            {
                Interlocked.CompareExchange<Delegate>( ref target, MakeDelegate(), null );
            }

            return target;
        }
    }

    /// <summary>
    /// Makes the delegate.
    /// </summary>
    /// <returns></returns>
    protected virtual Delegate MakeDelegate()
    {
        return DlrParameterResolver.MakeLambda<CallTarget>( code, "Invoker", null ).Compile();
    }

    /// <summary>
    /// Invokes the target with the specified scope.
    /// </summary>
    /// <param name="scope">The scope.</param>
    /// <param name="args">Optional arguments.</param>
    /// <returns>The result of the invocation.</returns>
    public virtual object Invoke( Scope scope )
    {
        return ( (CallTarget)Target )( scope, language, new CodeContext( scope, language ) );
    }

    /// <summary>
    /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
    /// </summary>
    /// <returns>
    /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
    /// </returns>
    public override string ToString()
    {
        return Code.ToString();
    }
}

ParameterizedInvoker:

public class ParameterizedInvoker : Invoker
{
    /// <summary>
    /// Initializes a new instance of the <see cref="Invoker&lt;T&gt;"/> class.
    /// </summary>
    /// <param name="code">The code.</param>
    /// <param name="language">The language.</param>
    public ParameterizedInvoker( Expression code, LanguageContext language, params ParameterExpression[] parameters )
        : base( code, language )
    {
        this.Parameters = parameters;
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="Invoker&lt;T&gt;"/> class.
    /// </summary>
    /// <param name="code">The code.</param>
    /// <param name="language">The language.</param>
    public ParameterizedInvoker( LambdaExpression code, LanguageContext language )
        : this( code.Body, language, code.Parameters.ToArray() )
    {
    }

    /// <summary>
    /// Gets or sets the parameters.
    /// </summary>
    /// <value>The parameters.</value>
    public IEnumerable<ParameterExpression> Parameters { get; set; }

    /// <summary>
    /// Makes the delegate.
    /// </summary>
    /// <returns></returns>
    protected override Delegate MakeDelegate()
    {
        var lambda = ( Code is LambdaExpression )
            ? (LambdaExpression)Code
            : DlrParameterResolver.MakeLambda( Code, "Invoker", Parameters.ToArray() );

        return lambda.Compile();
    }

    /// <summary>
    /// Invokes the target with the specified scope.
    /// </summary>
    /// <param name="scope">The scope.</param>
    /// <param name="args">Optional arguments.</param>
    /// <returns>The result of the invocation.</returns>
    public object Invoke( Scope scope, params object[] args )
    {
        return Target.DynamicInvoke( args );
    }
}

DlrParameterResolver (similar to GlobalRewriter in the DLR):

public class DlrParameterResolver : ExpressionVisitor
{
    private static DlrParameterResolver resolver = new DlrParameterResolver();

    public static readonly ParameterExpression ScopeParameter = Expression.Parameter( typeof( Scope ), "$scope" );
    public static readonly ParameterExpression LanguageParameter = Expression.Parameter( typeof( LanguageContext ), "$language" );
    public static readonly ParameterExpression ContextParameter = Expression.Parameter( typeof( CodeContext ), "$context" );

    /// <summary>
    /// Visits the children of the extension expression.
    /// </summary>
    /// <param name="node">The expression to visit.</param>
    /// <returns>
    /// The modified expression, if it or any subexpression was modified;
    /// otherwise, returns the original expression.
    /// </returns>
    /// <remarks>
    /// This can be overridden to visit or rewrite specific extension nodes.
    /// If it is not overridden, this method will call <see cref="M:Microsoft.Linq.Expressions.Expression.VisitChildren(Microsoft.Linq.Expressions.ExpressionVisitor)"/>,
    /// which gives the node a chance to walk its children. By default,
    /// <see cref="M:Microsoft.Linq.Expressions.Expression.VisitChildren(Microsoft.Linq.Expressions.ExpressionVisitor)"/> will try to reduce the node.
    /// </remarks>
    protected override Expression VisitExtension( Expression node )
    {
        var context = node as CodeContextExpression;

        if ( context != null ) return ContextParameter;

        return base.VisitExtension( node );
    }

    /// <summary>
    /// Visits the <see cref="T:Microsoft.Linq.Expressions.ParameterExpression"/>.
    /// </summary>
    /// <param name="node">The expression to visit.</param>
    /// <returns>
    /// The modified expression, if it or any subexpression was modified;
    /// otherwise, returns the original expression.
    /// </returns>
    protected override Expression VisitParameter( ParameterExpression node )
    {
        if ( node.Type == typeof( Scope ) ) return ScopeParameter;
        if ( node.Type == typeof( LanguageContext ) ) return LanguageParameter;

        return base.VisitParameter( node );
    }

    /// <summary>
    /// Makes the lambda builder.
    /// </summary>
    /// <param name="body">The body.</param>
    /// <param name="name">The name.</param>
    /// <param name="parameters">The parameters.</param>
    /// <returns></returns>
    private static LambdaBuilder MakeLambdaBuilder( Expression body, string name, params ParameterExpression[] parameters )
    {
        var builder = Utils.Lambda( typeof( object ), name );

        builder.AddParameters( ScopeParameter, LanguageParameter, ContextParameter );
        if ( parameters != null ) builder.AddParameters( parameters );

        var visited = resolver.Visit( body );

        builder.Body = ( visited.Type != typeof( object ) )
            ? Expression.Convert( visited, typeof( object ) )
            : visited;

        return builder;
    }

    /// <summary>
    /// Makes the lambda.
    /// </summary>
    /// <param name="body">The body.</param>
    /// <param name="parameters">The parameters.</param>
    /// <returns></returns>
    public static LambdaExpression MakeLambda( Expression body, string name, params ParameterExpression[] parameters )
    {
        return MakeLambdaBuilder( body, name, parameters ).MakeLambda();
    }

    /// <summary>
    /// Makes the lambda.
    /// </summary>
    /// <param name="body">The body.</param>
    /// <param name="parameters">The parameters.</param>
    /// <returns></returns>
    public static Expression<T> MakeLambda<T>( Expression body, string name, params ParameterExpression[] parameters )
    {
        return (Expression<T>)MakeLambdaBuilder( body, name, parameters ).MakeLambda( typeof( T ) );
    }
}

DlrParameterResolver is just an ExpressionVisitor the replaces any Scope, LanguageContext and CodeContext Expressions with known parameters (the static ParameterExpressions defined at the top).  The DLR’s GlobalRewriter does a similar thing, but defines a new top-level scope for the CodeContext.

Invoker compiles an Expression and caches the delegate (essentially the same thing as the DLR’s ScriptCode class).  ParameterizedInvoker just adds parameter support.  Invokers can be nested and used to create all kinds of interesting things…

I’ll include examples (and hopefully answer the giant “why would I want this?”) with the next part of my Excel Formulas and the DLR post, but let me know if you want more info before then :)

About these ads