在 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>
}
注意CountryNumber
和LocalNumber
都以终端<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.
我一直在理解 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>
}
注意CountryNumber
和LocalNumber
都以终端<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.