Java 使用 ANTLR 进行表达式解析
Java expression parsing with ANTLR
我正在 Java 中编写一个使用 Java 表达式解析的工具包。我想我会尝试使用 ANTLR,因为
- 它似乎被普遍用于这类事情
- 似乎没有很多开源替代品
- 我实际上曾尝试编写自己的通用解析器,但后来放弃了。那东西很难。
我不得不说,在我读了很多书并尝试了很多不同的东西之后(无论如何,比我预期的要多),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 需要更多的前瞻性来解决“歧义”。
我正在 Java 中编写一个使用 Java 表达式解析的工具包。我想我会尝试使用 ANTLR,因为
- 它似乎被普遍用于这类事情
- 似乎没有很多开源替代品
- 我实际上曾尝试编写自己的通用解析器,但后来放弃了。那东西很难。
我不得不说,在我读了很多书并尝试了很多不同的东西之后(无论如何,比我预期的要多),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 需要更多的前瞻性来解决“歧义”。