Java 使用 ANTLR 进行表达式解析

Java expression parsing with ANTLR

我正在 Java 中编写一个使用 Java 表达式解析的工具包。我想我会尝试使用 ANTLR,因为

  1. 它似乎被普遍用于这类事情
  2. 似乎没有很多开源替代品
  3. 我实际上曾尝试编写自己的通用解析器,但后来放弃了。那东西很难。

我不得不说,在我读了很多书并尝试了很多不同的东西之后(无论如何,比我预期的要多),ANTLR 似乎非常难用。 API 非常不直观——我永远不确定我的说法是否正确。

尽管 ANTLR 教程和示例比比皆是,但我没有找到任何涉及解析 Java“表达式”的示例——其他人似乎都想解析整个 java 文件。

我开始这样称呼它:

        Java8Lexer lexer = new Java8Lexer(CharStreams.fromString(text));
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        Java8Parser parser = new Java8Parser(tokens);
        ParseTree result = parser.expression();

但这不会解析整个表达式。例如。对于文本“a.b”,它会 return 一个只包含“a”部分的结果,在它可以解析的第一件事之后就退出。

很好。所以我改为:

        String input = "return " + text + ";";
        Java8Lexer lexer = new Java8Lexer(CharStreams.fromString(input));
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        Java8Parser parser = new Java8Parser(tokens);
        ParseTree result = parser.returnStatement();
        result = result.getChild(1);

认为这会迫使它解析整个表达式,然后我可以只提取我关心的部分。这适用于像“a.b”这样的名称表达式,但是如果我尝试解析像“a.b.c(d)”这样的方法表达式,它会给出一个错误:

line 1:12 mismatched input '(' expecting '.'

有趣的是,a()a.b()a.b.c 解析得很好,但 a.b.c() 也死于同样的错误。

这里有 ANTLR 专家知道我做错了什么吗?

另外,上面的错误被打印到 stderr,但我无法在任何地方的结果对象中找到它,这让我很困扰。我希望能够向输入表达式的用户显示该错误消息(尽管很模糊)——他们可能没有在看控制台,即使他们在看,那里也没有上下文。有没有办法在我返回的结果中找到该信息?

非常感谢任何帮助。

对于像expression这样的规则,ANTLR一旦识别出表达式就会停止解析。

您可以通过在开始规则中添加 `EOF 来强制它继续。

(您不想修改实际的 `expressions 规则,但您可以添加这样的规则:

expressionStart: expressions EOF;

那么你可以使用:

ParseTree result = parser.expressionStart();

这将强制 ANTLR 继续解析您的输入,直到它到达您输入的末尾。


回复:returnStatement

当我 运行 return a.b.c(); 通过 IntelliJ 中的 ANTLR 预览时,我得到了这个解析树:

有点遵守语法规则,我偶然发现了这些规则:

typeName: Identifier | packageOrTypeName '.' Identifier;

packageOrTypeName
    : Identifier
    | packageOrTypeName '.' Identifier
    ;

这两个规则都包含 packageOrTypeName '.' Identifier 的替代方案,我觉得有问题。

在树中,我们看到 primaryNoNewArray_lfno_primary:2 表示匹配此规则中的第二个备选方案:

primaryNoNewArray_lfno_primary
    : literal
    | typeName ('[' ']')* '.' 'class' // <-- trying to match this rule
    | unannPrimitiveType ('[' ']')* '.' 'class'
    | 'void' '.' 'class'
    | 'this'
    | typeName '.' 'this'
    | '(' expression ')'
    | classInstanceCreationExpression_lfno_primary
    | fieldAccess_lfno_primary
    | arrayAccess_lfno_primary
    | methodInvocation_lfno_primary
    | methodReference_lfno_primary
    ;

我现在没时间了,但会继续关注它。 Java8Parser.g4 中似乎不太可能存在这个明显的错误,但目前看来确实是一个错误。我不确定上下文会如何改变它的解析方式(根据上下文,意思是 returnStatement 在语法中被本地调用的地方。)

我尝试了这个输入(从 compilationUnit 规则开始:

class Test {
    class A {
       public B  b;
    }
    class B {
        String c() {
            return "";
        }
    }
    String test() {
        A a = new A();
        return a.b.c();
    }
}

它解析正确(因此,我们没有发现 Java8Parser 语法中的主要错误):

不过,这似乎不对。

越来越近:

如果我从 block 规则开始,并用花括号 ({return a.b.c();}) 括起来,它解析得很好。

我将采用这样的理论,即 ANTLR 需要更多的前瞻性来解决“歧义”。