我使用官方Java 8语言规范中定义的语法来编写Java解析器。
在我的.jj文件中,我有所有常见的选择冲突,例如
警告:选择冲突涉及两次扩展 第25行,第3列和第31行,第3列。 常见的前缀是: 考虑使用2的前瞻来进行早期扩展。
或
警告:第25行第8行(...)*构造中的选择冲突。
我确实从JavaCC仔细阅读了Lookahead教程但我的问题是每当我设置LOOKAHEAD(n),其中n> 1,我编译.jj文件,编译卡住了,我需要杀死java进程。
为什么?
CODE
由于我无法本地化导致我的问题的代码,我也无法隔离相应的代码部分。
我能够限制搜索错误的代码片段,如下所示:
我已在scribd here上传了代码。
请注意:
// OK
评论。这意味着当我只有这些规则时,我会从编译器得到警告,我有选择冲突但是当我添加时
LOOKAHEAD(3)
在相应的位置警告消失。答案 0 :(得分:2)
你的语法远离LL(1),很难知道从哪里开始。我们来看看类型。在纠正它以遵循JLS 8中的语法后,你有
void Type() :
{ }
{
PrimitiveType() |
ReferenceType()
}
其中
void PrimitiveType() :
{ }
{
(Annotation())* NumericType() |
(Annotation())* <KW_boolean>
}
void ReferenceType() :
{ }
{
ClassOrInterfaceType() |
TypeVariable() |
ArrayType()
}
void ClassOrInterfaceType() :
{ }
{
(Annotation())* <Identifier> (TypeArguments())? |
(Annotation())* <Identifier> (TypeArguments())? M()
}
Type
的错误是
Warning: Choice conflict involving two expansions at
line 796, column 3 and line 797, column 3 respectively.
A common prefix is: "@" <Identifier>
Consider using a lookahead of 3 or more for earlier expansion.
错误消息可以准确地告诉您问题所在。在Type
中,两个备选方案的开头都可以注释。解决这个问题的一种方法是分解出什么是常见的,即注释。
现在你有了
void Type() :
{ }
{
( Annotation() )*
( PrimitiveType() | ReferenceType() )
}
void PrimitiveType() :
{ }
{
NumericType() |
<KW_boolean>
}
void ReferenceType() :
{ }
{
ClassOrInterfaceType() |
TypeVariable() |
ArrayType()
}
void ClassOrInterfaceType() :
{ }
{
<Identifier> (TypeArguments())? |
<Identifier> (TypeArguments())? M()
}
这解决了Type
的问题。仍然有很多问题,但现在还有一个问题。
例如,ReferenceType
中的所有三个选项都可以以标识符开头。最后你会想要这样的东西
void Type() :
{ }
{
( Annotation() )*
( PrimitiveType() | ReferenceTypesOtherThanArrays() )
( Dims() )?
}
void PrimitiveType() :
{ }
{
NumericType() | <KW_boolean>
}
void ReferenceTypesOtherThanArrays() :
{ }
{
<Identifier>
( TypeArguments() )?
(
<Token_Dot>
( Annotation() )*
<Identifier>
( TypeArguments() )?
)*
}
请注意TypeVariable
已消失。这是因为无法在语法上区分类型变量和类(或接口)名称。因此,上面的语法将接受,例如T.x
,其中T
是一个类型变量,而JLS语法则不然。这是一种只能使用符号表排除的错误。在Java中有一些像这样的情况;例如,如果没有符号表,则无法通过变量名称从类名或类名中分辨包名;在表达式a.b.c
中,a
可以是包名,类名,接口名,类型变量,变量或字段名。
您可以通过以下两种方式之一处理这些问题:您可以在解析后处理问题,即在稍后的阶段,或者您可以在解析阶段使用符号表并使用符号表来指导解析器使用语义预测。然而,后一种选择对Java来说不是一个好选择;最好先解析并稍后处理需要符号表的所有问题。这是因为,在Java中,符号可以在使用后声明。它甚至可能在另一个文件中声明。我们在教学机器的Java编译器中所做的是首先解析所有文件。然后构建一个符号表。然后进行语义分析。当然,如果您的应用程序不需要诊断所有错误,那么这些注意事项可以在很大程度上被忽略。