Можно ли интерпретировать дерево выражений C # для испускания JavaScript? - PullRequest
27 голосов
/ 14 мая 2011

Например, если у вас есть такое выражение:

Expression<Func<int, int>> fn = x => x * x;

Есть ли что-нибудь, что пройдет через дерево выражений и сгенерирует это?

"function(x) { return x * x; }"

Ответы [ 5 ]

30 голосов
/ 14 мая 2011

Это, вероятно, не легко, но да, это абсолютно выполнимо. ORM, такие как Entity Framework или Linq to SQL, делают это для перевода запросов Linq в SQL, но на самом деле вы можете генерировать все что угодно из дерева выражений ...

Вы должны реализовать ExpressionVisitor для анализа и преобразования выражения.


РЕДАКТИРОВАТЬ: вот очень простая реализация, которая работает для вашего примера:

Expression<Func<int, int>> fn = x => x * x;
var visitor = new JsExpressionVisitor();
visitor.Visit(fn);
Console.WriteLine(visitor.JavaScriptCode);

...

class JsExpressionVisitor : ExpressionVisitor
{
    private readonly StringBuilder _builder;

    public JsExpressionVisitor()
    {
        _builder = new StringBuilder();
    }

    public string JavaScriptCode
    {
        get { return _builder.ToString(); }
    }

    public override Expression Visit(Expression node)
    {
        _builder.Clear();
        return base.Visit(node);
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        _builder.Append(node.Name);
        base.VisitParameter(node);
        return node;
    }

    protected override Expression VisitBinary(BinaryExpression node)
    {
        base.Visit(node.Left);
        _builder.Append(GetOperator(node.NodeType));
        base.Visit(node.Right);
        return node;
    }

    protected override Expression VisitLambda<T>(Expression<T> node)
    {
        _builder.Append("function(");
        for (int i = 0; i < node.Parameters.Count; i++)
        {
            if (i > 0)
                _builder.Append(", ");
            _builder.Append(node.Parameters[i].Name);
        }
        _builder.Append(") {");
        if (node.Body.Type != typeof(void))
        {
            _builder.Append("return ");
        }
        base.Visit(node.Body);
        _builder.Append("; }");
        return node;
    }

    private static string GetOperator(ExpressionType nodeType)
    {
        switch (nodeType)
        {
            case ExpressionType.Add:
                return " + ";
            case ExpressionType.Multiply:
                return " * ";
            case ExpressionType.Subtract:
                return " - ";
            case ExpressionType.Divide:
                return " / ";
            case ExpressionType.Assign:
                return " = ";
            case ExpressionType.Equal:
                return " == ";
            case ExpressionType.NotEqual:
                return " != ";

            // TODO: Add other operators...
        }
        throw new NotImplementedException("Operator not implemented");
    }
}

Он обрабатывает лямбда-выражения только с одной инструкцией, но в любом случае компилятор C # не может сгенерировать дерево выражений для лямбда-блоков.

Конечно, предстоит еще много работы, это очень минимальная реализация ... вам, вероятно, нужно добавить вызовы методов (VisitMethodCall), доступ к свойствам и полям (VisitMember) и т. Д.

7 голосов
/ 14 мая 2011

Script # используется внутренними разработчиками Microsoft, чтобы сделать именно это.

3 голосов
/ 28 марта 2015

Взгляните на Lambda2Js , библиотеку, созданную Мигелем Анджело для этой конкретной цели.

Добавляет метод расширения CompileToJavascript к любому выражению.

Пример 1:

Expression<Func<MyClass, object>> expr = x => x.PhonesByName["Miguel"].DDD == 32 | x.Phones.Length != 1;
var js = expr.CompileToJavascript();
Assert.AreEqual("PhonesByName[\"Miguel\"].DDD==32|Phones.length!=1", js);

Пример 2:

Expression<Func<MyClass, object>> expr = x => x.Phones.FirstOrDefault(p => p.DDD > 10);
var js = expr.CompileToJavascript();
Assert.AreEqual("System.Linq.Enumerable.FirstOrDefault(Phones,function(p){return p.DDD>10;})", js);

Дополнительные примеры здесь .

1 голос
/ 14 мая 2011

Несколько человек разработали библиотеки с открытым исходным кодом, чтобы решить эту проблему.Я смотрю на Linq2CodeDom , который преобразует выражения в граф CodeDom, который затем можно скомпилировать в JavaScript, если код совместим.

Script # использует оригиналИсходный код C # и скомпилированная сборка, а не дерево выражений.

Я внес небольшие изменения в Linq2CodeDom, чтобы добавить JScript в качестве поддерживаемого языка - по сути, просто добавив ссылку на Microsoft.JScript, обновив enum идобавив еще один случай в GenerateCode.Вот код для преобразования выражения:

var c = new CodeDomGenerator();
c.AddNamespace("Example")
    .AddClass("Container")
    .AddMethod(
        MemberAttributes.Public | MemberAttributes.Static,
        (int x) => "Square",
        Emit.@return<int, int>(x => x * x)
    );
Console.WriteLine(c.GenerateCode(CodeDomGenerator.Language.JScript));

И вот результат:

package Example
{
    public class Container
    {
        public static function Square(x : int)
        {
            return (x * x);
        }
    }
}

Подпись метода отражает более строго типизированную природу JScript.Может быть лучше использовать Linq2CodeDom для генерации C #, а затем передать это в Script #, чтобы преобразовать это в JavaScript.Я полагаю, что первый ответ является наиболее правильным, но, как вы можете видеть, просмотрев исходный код Linq2CodeDom, при обработке каждого случая требуется много усилий для правильной генерации кода.

1 голос
/ 14 мая 2011

Выражение уже было проанализировано для вас компилятором C #;остается только пройти дерево выражений и сгенерировать код.Обход дерева может быть выполнен рекурсивно, и каждый узел можно обработать, проверив, какой он тип (есть несколько подклассов Expression, представляющих, например, функции, операторы и поиск членов).Обработчик для каждого типа может генерировать соответствующий код и обходить дочерние узлы (которые будут доступны в различных свойствах в зависимости от того, какой тип выражения это).Например, узел функции может быть обработан с помощью первого вывода «function (», за которым следует имя параметра, за которым следует «) {».Затем тело может быть обработано рекурсивно, и, наконец, вы получите «}».

...