在 JAVACC 中声明 LOOKAHEAD 的不同方式

Different ways to declare LOOKAHEAD in JAVACC

我一直在理解 Javacc 语法来编写解析器,我在其中找到了一行,它告诉我

Options : { LOOKAHEAD=3; }

我想知道什么是 LOOKAHEAD,是否有任何不同的方法来声明前瞻?

LOOKAHEAD 选项是默认的标记数,用于决定在每个选择点选择哪条路径。

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

LOOKAHEAD 选项的默认值为 1。因此编写适合 LOOKAHEAD=1 的语法很重要,这意味着选择将通过向前看一个标记来解决。

例如:

由于JavaCC是LL解析器,如果产生式规则的左记号重复,会出现选择冲突:

void PhoneNumber() : {} {​

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

}​

void LocalNumber() : {} { ​

AreaCode() "-" <FOUR_DIGITS>  ​

}​

void CountryNumber() : {} {  ​

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

}​

void AreaCode() : {} {   ​

 <THREE_DIGIT> ​

}

注意CountryNumberLocalNumber都以终端<THREE_DIGIT>开头,导致选择冲突,

这可以通过一种称为 "left​ factoring​."

的方法重写
void PhoneNumber() : {} {​

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

}​

void LocalNumber() : {} { ​

<FOUR_DIGITS>  ​

}​

void CountryNumber() : {} {  ​

<THREE_DIGIT> "-" <FOUR_DIGITS>   ​

}​

void AreaCode() : {} {   ​

 <THREE_DIGIT> ​

}

对于无法做到这一点的情况,使用 Lookahead 规范

前瞻规范有五种类型,

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

Multiple token LOOKAHEAD

这可以与在 LOOKAHEAD(k) 方法中传递的整数一起使用。可以通过,

  • 本地 LOOKAHEAD

    void PhoneNumber() : {} { ( LOOKAHEAD(3) LocalNumber() |国家编号() ) }

  • 全球前瞻

    选项:{ LOOKAHEAD=3; }

Syntactic LOOKAHEAD

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

当指定不带多个标记的语法先行时,您真正指定的是具有无限多个标记的语法先行。

例如:LOOKAHEAD(2147483647, LocalNumber())

void PhoneNumber() : {} {​

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

| CountryNumber()​

) <EOF> ​

}

Combination of Multiple token and Syntactic LOOKAHEAD

有了这个我们就可以先行有限数量的标记,只要满足指定选择的语法先行。

void PhoneNumber() : {} {​

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

| CountryNumber()​

) <EOF> ​

}

Semantic LOOKAHEAD

语义前瞻涉及在选择点的语法中嵌入 Java 布尔表达式。

如果布尔表达式的计算结果为真,则选择当前扩展。

void PhoneNumber() : {} {​

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

| FarmvilleNumber()​

) <EOF> ​

}

Nested Lookahead

当一个先行指令与另一个指令重叠时,就会发生嵌套先行。 在 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 生成解析器

希望这有用,谢谢。

目前JavaCC最高级的版本是JavaCC 21.

JavaCC21 提供了一种指定前瞻的新方法,它比遗留 JavaCC 项目中的方法多 user-friendly 而少 error-prone。我指的是 up-to-here 分隔符。

如果你有这样的作品:

void FooBar() : {}
{
   "foo" "bar" Baz()
}

你想提前扫描2个token看是否进入一个FooBar(),在legacy JavaCC中,你写:

LOOKAHEAD(2) FooBar()

每次使用 FooBar() 产生式。或者你可以写:

LOOKAHEAD("foo" "bar") FooBar()

在JavaCC21中,可以这样写产生式:

 void FooBar() : {}
 {
    "foo" "bar" =>|| Baz()
 }

=>|| 被称为 up-to-here 分隔符,意味着我们扫描到这一点。如果像上面这样写产生式,你可以简单地写成:

 FooBar()
 |
 SomethingElse()

并且生成的解析器在决定是否进入 FooBar() 时扫描到生产中指定的点。

up-to-here 定界符通常可以让您以更轻松的方式表达事物。例如,在遗留工具中,您会写 maybe:

 LOOKAHEAD (Foo() Bar()) Foo() Bar() Baz()

而在 JavaCC21 中,您可以这样写:

 Foo() Bar() =>|| Baz()

JavaCC21 也有一个(可选的)流线型语法,因此您可以更简洁地编写上面的 FooBar 产品:

  FooBar : "foo" "bar" =>|| Baz ;

当然,*up-to-here" 语法几乎不是 JavaCC21 中遗留工具中不存在的唯一新功能。这里要概述的内容太多了。真正的大问题是嵌套语法 e ]lookahead 现在可以在 JavaCC21 中使用。参见 here.