语法选择冲突
在解释什么事语法选择冲突之前,先看一个语文相关的不太恰当的例子,这个例子对理解语法选择冲突有帮助。
特别是2,3的,他们的前3字都是一样的,但是他们的含义是不同的,2是“为” “了解” ,3是“为了” “解决”。对于人的思维来说能够快速的分辨出其中的区别,但是对计算机来说并不简单。如果能明白上面的例子,那么差不多就能理解javacc中的语法选择冲突大概是怎么一会事了。
现在就看一下javacc出现了语法选择冲突的例子。
void parse():
{
}
{
assignExp()
| functionExp()
}
void assignExp():
{
Token t;
double v;
}
{
t=<ID> <EQUALS> v=calc()
{
//TODO 实现赋值表达式的动作
System.out.println(t.image+"="+v);
}
}
void functionExp():
{
Token t;
}
{
<ID> <LPAREN> (<ID>
(<COMMA> <ID>)*
)?
<RPAREN>
}
在对这个jj文件执行javacc命令时候,会抛出这样的提示信息
Warning: Choice conflict involving two expansions at
line 66, column 4 and line 67, column 6 respectively.
A common prefix is: <ID>
Consider using a lookahead of 2 for earlier expansion.
这提示就是告诉你,有两个选择冲突,因为他们的都是以<ID>token开头的,以及他们在语法文件的位置信息,并建议你用lookahead去处理这个冲突。也就说当javacc解析到<ID>时,有两条路可以走,但是这条路的路标和路口太相似了,javacc不知道该选着哪条路。
说白了就是javacc默认是生成LL(1) 解析器,为了保持解析性能,javacc不会自动的去处理这种需要向前探索才能确定语法分支的事情,但是它提供了lookahead让用户主动指定要如何向前探索功能,通过此功能,javacc就可以处理上面的语法选择冲突。
LOOKAHEAD
顾名思义就是展望,前瞻探索的含义。
当消费token的语法指针P在token1的位置时碰到了lookahead命令,就会派出L指针前去探查后面的token是否符号lookaheand的定义,如果符合就选择该语法分支,不符合就选择别的分支。
lookahead的构造方式如下
LOOKAHEAD ( amount, expansion, { boolean_expression } )
使用时必须至少存在三个条件中的一个。如果存在多个,则用逗号分隔。
amount 表示要展望的token数量,当expansion存在时,而没有明确指定amount 时默认是2147483647,其他情况,如果没有在options里指定默认的lookahead的amount数量则默认是0。
expansion 表示要展望的匹配的规则表达式,默认是当前分支要扩展的规则。
boolean_expression 布尔表达式,默认是true。
例子1:
//还是以解决上面的冲突为例子
void parse():
{
}
{
LOOKAHEAD(2)
assignExp()
| functionExp()
}
通过在第一个分支上加上了lookahead(2)解决了语法选择冲突,这个lookahead的含义是,先去展望2个token,看看这两个token是否跟 <ID> "=" 匹配,此时它等价与
LOOKAHEAD(2,<ID> <EQUALS>)
例子2:
void TypeDeclaration() :
{}
{
LOOKAHEAD(ClassDeclaration())
ClassDeclaration()
|
InterfaceDeclaration()
}
这个lookahead的含义是,先去展望token,直到展望的token完全匹配ClassDeclaration的定义并且展望的token数小于2147483647。其实不建议这样使用lookahead,这样的效率是比较低下,应该尽可能的完善lookahead的匹配条件,例如可以改成这样
void TypeDeclaration() :
{}
{
LOOKAHEAD(10, ( "abstract" | "final" | "public" )* "class" )
ClassDeclaration()
|
InterfaceDeclaration()
}
例子3:
void BC() :
{}
{
"b"
[ LOOKAHEAD( { getToken(1).kind == C && getToken(2).kind != C } )
<C:"c">
]
}
其含义,当消费token到“b”时,先去执行一下布尔表达式,看看是否符号布尔表达式条件,符号就往下走对应的语法分支。这个 布尔表达式是先判断一下下一个(getToken(1).kind)==c并且下下个(getToken(2).kind)!= c。