Javacc解析器选项LOOKAHEAD,Java

时间:2010-02-20 16:46:25

标签: java javacc

我最近开始使用javacc开始使用语法分析器,其中一个字段是选项...我有一个类似下面的代码:

options
{
  LOOKAHEAD=1;
}
PARSER_BEGIN(Calculator)

public class Calculator
{
 ...
}
PARSER_END(Calculator)

LOOKAHEAD选项究竟是什么意思? 感谢

4 个答案:

答案 0 :(得分:8)

JavaCC创建递归下降解析器。这种类型的解析器通过查看下一个符号来决定选择哪个规则。默认情况下,它只查看下一个符号(lookahead = 1)。但是您可以将解析器配置为不仅可以查看下一个,还可以查看下一个N个符号。如果将lookahead设置为2,则生成的解析器将查看接下来的两个符号以决定选择哪个规则。这样,您可以更自然地定义语法,但代价是性能。前瞻越大,解析器就越需要做。

如果将一般预测设置为更大的数字,则所有输入的解析器都会变慢(对于非平凡的语法)。如果您希望默认情况下使用lookahead = 1的解析器,并且仅在特定情况下使用更大的前瞻,则可以在本地使用前瞻。

http://www.engr.mun.ca/~theo/JavaCC-FAQ/javacc-faq-moz.htm#tth_sEc4.5

例如,具有lookahead = 1的解析器无法决定采用哪个规则(1或2),但是使用lookahead = 2它可以:

void rule0() : {} { 
  <ID> rule1() 
| <ID> rule2()
}

您可以更改语法的定义以获得相同的结果,但使用lookahead = 1:

void rule0() : {} { 
  <ID> ( rule1() | rule2() )
}

答案 1 :(得分:7)

请参阅http://en.wikipedia.org/wiki/Lookahead#Lookahead_in_parsing

通常,解析器只查看下一个标记以确定要应用的生产规则。但是,在某些情况下,这还不足以做出选择。例如,给出两个生产规则:

p0: foo -> identifier "=" expr
p1: bar -> identifier "(" arglist ")"

如果下一个标记的类型为identifier,则解析器无法确定它是否应使用foobar生成。然后JavaCC会出错,说它需要使用更多的前瞻。将前瞻改为2意味着允许解析器查看接下来的两个令牌,在这种情况下足以在两个令牌之间进行选择。

史蒂夫指出,这是在javacc文档中:https://javacc.org/tutorials/lookahead

答案 2 :(得分:2)

LOOKAHEAD值告诉生成的解析器有多少未处理(即未来)的令牌用于决定要转换到的状态。在严格约束的语言中,只需要一个先行令牌。语言越模糊,需要越多的前瞻标记来确定要进行哪种状态转换。

我认为这在javacc(1)教程中有所涉及。

答案 3 :(得分:0)

LOOKAHED是在Javacc解析器生成的语法树中对语法树中的语法验证进行决策所需的决策路径所需的令牌数量。

由于javacc不支持回溯,因此需要此配置来解决选择冲突。

因此写出适合LOOKAHED = 1的语法很重要,这意味着通过查看一个标记,可以确定语法树中的流。这是一种编写语法的方式。

例如:

由于JavaCC是LR解析器,因此会重复产生规则的左令牌,因此在syntex树上将是分支。选择冲突出现在选择哪个分支上,

void PhoneNumber() : {} {​

(LocalNumber() | CountryNumber()) <EOF> ​

}​

void LocalNumber() : {} { ​

AreaCode() "-" <FOUR_DIGITS>  ​

}​

void CountryNumber() : {} {  ​

AreaCode() "-" <THREE_DIGIT> "-" <FOUR_DIGITS>   ​

}​

void AreaCode() : {} {   ​

 <THREE_DIGIT> ​

}

请注意,CountryNumber和LocalNumber均以终端<THREE_DIGIT>开头,这是选择冲突,

这可以通过称为“左因子分解”的方法来重写。

void PhoneNumber() : {} {​

AreaCode() "-" (LocalNumber() | CountryNumber()) <EOF> ​

}​

void LocalNumber() : {} { ​

<FOUR_DIGITS>  ​

}​

void CountryNumber() : {} {  ​

<THREE_DIGIT> "-" <FOUR_DIGITS>   ​

}​

void AreaCode() : {} {   ​

 <THREE_DIGIT> ​

}

对于无法完成的情况,使用Lookahead

先行者有五种类型,

  • 多个令牌LOOKAHEAD
  • 语法LOOKAHEAD
  • 多个令牌和语法LOOKAHEAD的组合
  • 语义LOOKAHEAD
  • 嵌套前瞻
  

多个令牌LOOKAHEAD

可以与在LOOKAHEAD(k)方法中传递的整数一起使用,可以通过以下方式完成

  • 本地LOOKAHEAD

    void PhoneNumber():{} { ( LOOKAHEAD(3)LocalNumber() | CountryNumber() ) }

  • 全局LOOKAHEAD

    选项:{LOOKAHEAD = 3; }

  

语法LOOKAHEAD

语法先行规范使用语法构造作为选择解析器。

在不使用多个令牌的情况下指定语法先行时,您真正要指定的是在无限多个令牌中的语法先行。

例如:LOOKAHEAD(2147483647,LocalNumber())

void PhoneNumber() : {} {​

(​
LOOKAHEAD(("A"|"B")+ AreaCode() "-" <FOUR_DIGITS>) LocalNumber() ​

| CountryNumber()​

) <EOF> ​

}
  

多个令牌和语法LOOKAHEAD的组合

通过这种方式,只要可以在指定的选择范围内满足语法先行的方式,就可以先行有限数量的令牌。

void PhoneNumber() : {} {​

(​
LOOKAHEAD(10, ("A"|"B")+ AreaCode() "-" <FOUR_DIGITS>) LocalNumber() ​

| CountryNumber()​

) <EOF> ​

}

  

语义LOOKAHEAD

语义超前涉及在选择点将Java代码嵌入语法中。

如果Java代码返回true,则选择当前扩展名。

void PhoneNumber() : {} {​

(​
LOOKAHEAD({getToken(1).image.equals("123")}) ​
KeysvilleNumber() ​

| FarmvilleNumber()​

) <EOF> ​

}

  

嵌套前瞻

嵌套先行发生在一个先行指令与另一个先行指令重叠时。 在JavaCC中,嵌套先行会被忽略,但嵌套语义先行不会被忽略。

void Start() : {} {​

(​
LOOKAHEAD(Fullname()) Fullname() ​

| Douglas()​

) <EOF> ​

}​

void Fullname() : {} {​

( LOOKAHEAD(Douglas() Munro()) ​

 Douglas()​

| Douglas() Albert()​

)​

Munro() ​

}​

void Douglas() : {} { "Douglas" } ​

void Albert() : {} { "Albert" }}​

void Munro() : {} { "Munro" }}

所有示例均摘自Tom Copeland的超赞书籍使用JavaCC生成解析器

希望这很有用,谢谢。