обходя ast со сложным условным выражением для генерации выражения linq - PullRequest
2 голосов
/ 28 января 2012

Я использую Irony.net для генерации дерева разбора из исходного кода.По сути, я использую ExpressionEvaluatorGrammer как грамматику для двоичных выражений (арифметических, реляционных и логических / условных).Я хочу преобразовать результирующее дерево разбора в выражение Linq, пройдя его.Тем не менее, у дерева, по-видимому, нет формации, непосредственно преобразуемой в условное выражение linq.Гипотетический пример такого выражения:

1 == 1 && 4 - 1 == 3

генерирует (псевдо-xml дерево для краткости):

<binary>
  <binary>
    <binary>
      <literal>1</literal>
      <op>==</op>
      <literal>1</literal>
    </binary>
    <op>&&</op>
    <binary>
      <literal>4</literal>
      <op>-</op>
      <literal>1</literal>
    </binary>
  </binary>
  <op>==</op>
  <literal>3</literal>
</binary>

В дереве выше арифметическое выражение (4 - 1) становится правильнымвыражение для логической операции &&, поскольку родительский узел закрывается после него.В идеальном мире это должно было быть левое выражение узлов, представляющих "== 3".

Как вы проходите такое дерево, чтобы генерировать правильное и действующее?Или есть способ создать дерево в желаемой форме?

Правка: вот определение грамматики (частичное).Я взял его из ExpressionEvaluatorGrammer, который поставляется с Irony.interpreter.

RegisterOperators(15, "&", "&&", "|", "||");
RegisterOperators(20, "==", "<", "<=", ">", ">=", "!=");
RegisterOperators(30, "+", "-");
RegisterOperators(40, "*", "/");
Expr.Rule = Term
Term.Rule = number | ParExpr | stringLit | FunctionCall | identifier | MemberAccess | IndexedAccess;
ParExpr.Rule = "(" + Expr + ")";
BinExpr.Rule = Expr + BinOp + Expr;
BinOp.Rule = ToTerm("+") | "-" | "*" | "/" | "**" | "==" | "<" | "<=" | ">" | ">=" | "!=" | "&&" | "||" | "&" | "|";

Ответы [ 2 ]

1 голос
/ 28 января 2012

Вы не можете это исправить, обходя дерево магическим / особым образом. Ваш парсер неверен! Вероятно, это просто неправильно настроено. Вам необходимо получить правильное дерево для дальнейшей обработки.

Возможно, у вас неправильные правила приоритета операторов. Похоже, по крайней мере. Попробуйте добавить скобки, чтобы увидеть, исправило ли это дерево.

0 голосов
/ 28 января 2012

Предполагая, что приоритет оператора правильный , вы должны рекурсивно обходить дерево, используя шаблон посетителя, возвращая Expression на каждом уровне:

XName xBinary = "binary";
XName xLiteral = "literal";
Expression Visit(XElement elt)
{
    if (elt.Name == xBinary)
    {
        return VisitBinary(elt);
    }
    else if (elt.Name == xLiteral)
    {
        return VisitLiteral(elt);
    } // ...

    throw new NotSupportedException();
}

Теперь, когда у вас есть структура Visit, вы просто пишете каждому конкретному посетителю использовать свой основной Visit:

Expression VisitLiteral(XElement elt)
{
    Debug.Assert(elt.Name == xLiteral);
    return Expression.Constant((int)elt);
}

Expression VisitBinary(XElement elt)
{
    Debug.Assert(elt.Name == xBinary);
    Debug.Assert(elt.Elements().Count() >= 3);

    var lhs = elt.Elements().ElementAt(0);
    var op = elt.Elements().ElementAt(1);
    var rhs = elt.Elements().ElementAt(2);

    switch((string)op)
    {
    case "+":
        // by chaining LHS and RHS to Visit we allow the tree to be constructed
        // properly as Visit performs the per-element dispatch
        return Expression.Add(Visit(lhs), Visit(rhs));
    case "&&":
        return Expression.AndAlso(Visit(lhs), Visit(rhs));
    default:
        throw new NotSupportedException();
    }
}
...