0

我继承了一个 Antlr 4 解析器(在 Java 中),它使用侦听器方法,但有一些需要特殊情况的 walker。

背景资料:

当您在 Antlr (v4) 中编写解析器语法时,它会为您生成一些 Java 类,一个用于您的每个解析器非终端。这些是类,例如: PowerShellParser.ForEachObjectStatementContext对于定义 a 的解析器规则ForEach Statement。对于合理大小的语法,您实际上会获得数十个(通常接近 100 个类)。它们是 Java 类层次结构的一部分,如下所示:

ParseTree
  |
  + - ErrorNode // used when there are errors in the program being compiled
  |
  + - TerminalNode // tokens, e.g. identifiers, '+', '=', whitespace, constants
  |
  * - RuleNode // non-terminals, subclassed further
        |
        + - ProgramContext  // for the Program rule/non-terminal
        |
        + - ForEachStatementContext // for the ForEach Statement rule
        |
        + - IfThenElseStatementContent // if (expr) then-clause else-clause?
        | 
        + - BlockStatementContext  // { statements* }
        |
        + - AssignmentStatementContext // var = expression
        |
        + - AddExpressionContext // expression + expression
        |
 
and literally many, many more.

并且 antlr 还生成了一个“侦听器”模式,以便轻松定义这些不同代码位的语义。此模式由上述类树和两个可以更改的默认实现的函数组成。walker功能和功能listener。正常的 walker 函数会做以下事情,调用监听器的“enter(t)”函数,递归遍历树中的孩子,并调用监听器的 exit(t) 函数。

https://github.com/antlr/antlr4/blob/master/runtime/Java/src/org/antlr/v4/runtime/tree/ParseTreeWalker.java的原始 antlr 代码 大致如下所示:


public class Walker {

    public void walk(ParseTree t) {
        if (t instanceof ErrorNode) {
            listener.visitErrorNode((ErrorNode)t);
        } else if (t instanceof TerminalNode) {
            listener.visitTerminalNode((TerminalNode)t);
        } else if (t instanceof RuleNode) {
            RuleNode r = (RuleNode)t;
            ParserRuleContext ctx = 
                (ParserRuleContext)r.getRuleContext();
            listener.enterEveryRule(ctx);
            listener.enterRule(r);          // Here we call the listener
            int n = r.getChildCount();
            for (Integer i = 0; i < n; ++i) {
                ParseTree child = r.getChild(i);
                walk(child);                // Here we recursively call ourselves
                }  
             listener.exitRule(r);          // Here we call the listener again
        }
    }

现在,在侦听器中,您编写如下代码:

public class listener {

   public void enterRule(PowerShellParser.ProgramContext ctx) {
      // Here we are starting a program and write whatever semantics we need.
   }

   public void exitRule(PowerShellParser.ProgramContext ctx) {
      // Here we are done with a program and write whatever semantics we need.
   }

对于所有具有您关心的语义的类,依此类推。您可以忽略其中一些功能,默认情况下不执行任何操作。

这适用于“表达式”,因为如果您将代码放在退出函数中,您将获得一种自下而上评估表达式的好方法,基本上是免费的。

但是,这样的遍历不适用于 if 语句或 for 循环等。因为您不想盲目地遍历每个节点。

因此,编写我继承的代码的人修改了 walker 代码,如下所示。它可以工作,但它有这一层 if 语句,当我添加更多类型的语句时,我必须不断扩展。我希望对 walker 的递归调用像侦听器代码一样工作,调用与树中非终端类的“类型”匹配的版本,而不必在 if 语句中列出它们。在我的书中,检查对象的类型是一种“气味”。你应该只用正确的签名编写代码,它应该被调用。但这不会像 walker 调用侦听器并传递对象时那样在递归调用中发生。而且,我想知道如何解决它。


所以。因此我原来的问题:

然而,当 walker 递归地在孩子身上调用自己时,它不会选择特殊情况版本,并且作者必须插入代码来检查类型并调用正确的类型。需要此代码中的 ifs 做错了什么:

package walker;

import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.*;

public class Walker {

    public void walk(ParseTree t) {
        // why do I need this if statement, why isn't the right function called?
        if (t instanceof ErrorNode) {
            walk((ErrorNode)t);
        } else if (t instanceof TerminalNode) {
            walk((TerminalNode)t);
        } else if (t instanceof RuleNode) {
            walk((RuleNode)t);
        } else {
            notImplemented("walk ParseTree for " + t);
        }
    }

    public void walk(ErrorNode t) {
        listener.visitErrorNode(t);
    }

    public void walk(TerminalNode t) {
        listener.visitTerminal(t);
    }

    public void walk(RuleNode r) {
        // same issue, only worse
        if (r instanceof PowerShellParser.ForEachObjectStatementContext) {
            walk((PowerShellParser.ForEachObjectStatementContext)r);
            return;
        } else if (r instanceof PowerShellParser.ForEachStatementContext) {
            walk((PowerShellParser.ForEachStatementContext)r);
            return;
        } else if (r instanceof PowerShellParser.IfStatementContext) {
            walk((PowerShellParser.IfStatementContext)r);
            return;
        } else if (r instanceof PowerShellParser.DoWhileStatementContext) {
            walk((PowerShellParser.DoWhileStatementContext)r);
            return;
        } else if (r instanceof PowerShellParser.HardCase1Context) {
            walk((PowerShellParser.HardCase1Context)r);
            return;
        } else if (r instanceof PowerShellParser.HardCase2Context) {
            walk((PowerShellParser.HardCase2Context)r);
            return;
        } else if (r instanceof PowerShellParser.HiddenStringMethodExpressionContext) {
            walk((PowerShellParser.HiddenStringMethodExpressionContext)r);
            return;
        } else if (r instanceof PowerShellParser.InvokeCommandCommandContext) {
            walk((PowerShellParser.InvokeCommandCommandContext)r);
            return;
        } else if (r instanceof PowerShellParser.ExpressionListContext) {
            walk((PowerShellParser.ExpressionListContext)r);
            return;
        } else if (r instanceof PowerShellParser.JoinWithExpressionListContext) {
            walk((PowerShellParser.JoinWithExpressionListContext)r);
            return;
        }

        // this is all I want this function to do, provide a default traversal
        // when I don't have a specialized version
        ParserRuleContext ctx = (ParserRuleContext)r.getRuleContext();
        listener.enterEveryRule(ctx);
        enterRule(r);

        int n = r.getChildCount();

        for (Integer i = 0; i < n; ++i) {
            ParseTree child = r.getChild(i);  // I presume the issue is here
            walk(child);
        }

        exitRule(r);
        listener.exitEveryRule(ctx);
    }

    public void walk(PowerShellParser.ForEachObjectStatementContext r) {
         // I want recursive calls of this object to call this directly
         // and not go through the two ifs above.
         // E.g. this version walks its children multiple times
         // The if version only walks some of its children
         // The hard cases actually do something like "eval"
    }

    public void walk(PowerShellParser.ForEachStatementContext r) {
    }

    public void walk(PowerShellParser.IfStatementContext r) {
    }

    public void walk(PowerShellParser.DoWhileStatementContext r) {
    }

    public void walk(PowerShellParser.JoinWithExpressionListContext r) {
    }

    public void walk(PowerShellParser.QuotedProgramContext r) {
    }

    public void walk(PowerShellParser.HardCase1Context r) {
    }

    public void walk(PowerShellParser.HardCase2Context r) {
    }

}

4

1 回答 1

0

listener更仔细地研究了代码之后,我发现即使它也不能完全按照我的意愿行事。它将每个重命名enterRule(Xxxx ctx)enterXxxx(Xxxx ctx),并且 Xxxx 类中的代码将其重定向。因此,您不能轻松地定义一个在 Java 中以我想要的方式分派的函数,至少如果您要分派的类无法修改(在这种情况下,我不能),则不能。

于 2020-11-28T21:35:37.753 回答