【问题标题】:traversing an ast having complex conditional expression to generate linq expression遍历具有复杂条件表达式的 ast 以生成 linq 表达式
【发布时间】:2012-01-28 15:53:01
【问题描述】:

我正在使用 Irony.net 从源代码中生成解析树。本质上,我将 ExpressionEvaluatorGrammer 类似于 grammer 用于二进制表达式(算术、关系和逻辑/条件)。我想通过遍历将生成的解析树转换为 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”的节点的左表达式。

你如何遍历这样的树来生成正确的和操作?或者,有没有办法以我想要的形式生成树?

编辑:这是语法(部分)定义。我取自 Irony.interpreter 附带的 ExpressionEvaluatorGrammer。

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("+") | "-" | "*" | "/" | "**" | "==" | "<" | "<=" | ">" | ">=" | "!=" | "&&" | "||" | "&" | "|";

【问题讨论】:

  • 我不知道 Irony.Net 但它生成的 xml 对应于 ((1==1)&&(4*1))==3。要么是 && 和 == 具有相同的优先级,而 * 具有更高的优先级,要么是一个错误。您可能会尝试在解析之前添加括号,或使用其他工具。如果实际的语法不是更复杂,那么手动编写解析器(直接生成表达式)应该不难。
  • 您对RegisterOperators 的调用是什么样的?如何设置关联性?
  • @sixlettervariables 请看语法优先
  • 看来您的优先级是正确的,但语法不尊重== 应该比&amp;&amp; 绑定更紧密。您使用的是哪个版本的 Irony? author recently fixed a precedence/associativity bug.
  • @Ali 现在我的需求并不复杂(本质上我需要一个条件表达式解析器),但将来该语言可能会通过循环等进行扩展。如果它可以用 Irony 完成,我将能够轻松扩展它

标签: c# parsing abstract-syntax-tree irony


【解决方案1】:

您无法通过以魔法/特殊方式遍历树来解决此问题。您的解析器不正确!可能只是配置错误。您绝对需要从中获取正确的树才能进一步处理它。

可能你有错误的运算符优先规则。至少看起来是这样。尝试添加括号以查看它是否修复了树。

【讨论】:

    【解决方案2】:

    假设运算符优先级正确,您应该使用访问者模式递归遍历树,在每个级别返回一个 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();
        }
    }
    

    【讨论】:

    • 问题是,Irony.Net生成的xml“树”不正确。
    • @Ali:刚刚注意到,这听起来像是一个优先级问题。一旦优先级确定,OP 就可以使用访问者模式。
    • 升级解决了这个问题。谢谢!
    猜你喜欢
    • 2020-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多