为什么我的解析器生成报告说此 LALR(1) 文法不是 LALR(1)?
Why is my parser generating reporting that this LALR(1) grammar is not LALR(1)?
好的,我的解析器生成器之旅继续进行。这次弄到一个经典文法,据说是LALR文法:
start -> a a
a -> "A" a
a -> "B"
当我把它放入我的解析器生成器时,它给我输出:
LIST OF STATES:
-----------------------
<0: S' -> . start , $
start -> . a a , $
a -> . "A" a , "A" / "B"
a -> . "B" , "A" / "B"
{NTerm(start): 1, Term(A): 2, Term(B): 3, NTerm(a): 4}>(3104365621624877555)
--------------------
<1: S' -> start . , $
{}>(3969511602615904846)
--------------------
<2: a -> "A" . a , "A" / "B"
a -> . "A" a , "A" / "B"
a -> . "B" , "A" / "B"
{Term(A): 2, Term(B): 3, NTerm(a): 5}>(5490562805113673592)
--------------------
<3: a -> "B" . , "A" / "B"
{}>(-4845209343945471034)
--------------------
<4: start -> a . a , $
a -> . "A" a , $
a -> . "B" , $
{Term(A): 6, Term(B): 7, NTerm(a): 8}>(598157158659875896)
--------------------
<5: a -> "A" a . , "A" / "B"
{}>(436327415052220213)
--------------------
<6: a -> "A" . a , $
a -> . "A" a , $
a -> . "B" , $
{Term(A): 6, Term(B): 7, NTerm(a): 9}>(5490562805113673592)
--------------------
<7: a -> "B" . , $
{}>(-4845209343945471034)
--------------------
<8: start -> a a . , $
{}>(5795088700656730485)
--------------------
<9: a -> "A" a . , $
{}>(436327415052220213)
POSSIBLE STATES TO JOIN: (2, 6), (3, 7), (5, 9)
ATTEMPTING CONVERSION TO LALR GRAMMAR...FAILED
CONTINUING WITH CLR(1)...
这些状态与我在其他来源中读到的有关编译 LALR 语法的内容相匹配——这一步看起来不错,它会产生正确的状态,就像我手动完成一样。生成器建议——这也是其他来源所说的关于将 CLR(1) 语法转换为 LALR 的说法——指出 (2,6)
、(3,7)
、(5,9)
可以连接,但它不能这样做所以。
当我查看生成的 action 和 goto 表时,我明白了原因:
如您所见,状态 2 和 6 无法合并,因为存在不兼容的项 s2 <> s6
、s3 <> s7
等等。
但最让我惊讶的是生成器完成了它的工作并生成了一个 运行ning 程序。当我运行这个程序测试数据时,它接受了数据!因此,我的生成器生成了正确的表格。
这是否意味着这种经典的 "LALR" 语法只有在人工手动编译时才是 LALR?我的解析器生成器有什么不同之处?
我不知道您的解析器生成器是做什么的,但标准解析器生成器可以处理该语法。例如,这里是野牛的状态转换 table,生成方式:
bison --report-file=aa.output --report=all --graph=aa.dot --output=/dev/null \
<(printf "%%%%\n%s" "start: a a; a: 'A' a | 'B'")
dot -o aa.png -Tpng aa.dot
(Bison 是一个非常方便的工具;即使您正在编写自己的解析器生成器,您也可以利用它的功能。另请参阅它的 XML 输出。)
这里是稍微编辑过的报告文件(我删除了终端和非终端列表,以及一些空行,以减少使用space。)
Grammar
0 $accept: start $end
1 start: a a
2 a: 'A' a
3 | 'B'
State 0
0 $accept: . start $end
1 start: . a a
2 a: . 'A' a
3 | . 'B'
'A' shift, and go to state 1
'B' shift, and go to state 2
start go to state 3
a go to state 4
State 1
2 a: . 'A' a
2 | 'A' . a
3 | . 'B'
'A' shift, and go to state 1
'B' shift, and go to state 2
a go to state 5
State 2
3 a: 'B' .
$default reduce using rule 3 (a)
State 3
0 $accept: start . $end
$end shift, and go to state 6
State 4
1 start: a . a
2 a: . 'A' a
3 | . 'B'
'A' shift, and go to state 1
'B' shift, and go to state 2
a go to state 7
State 5
2 a: 'A' a .
$default reduce using rule 2 (a)
State 6
0 $accept: start $end .
$default accept
State 7
1 start: a a .
$default reduce using rule 1 (start)
我认为问题出在这里:
As you can see states 2 and 6 can't be joined, because there are incompatible items s2 <> s6, s3 <> s7 and so on.
这实际上不太正确。请注意,在状态 2 中,shift 项分别将您带到状态 2 和 3。在状态 6 中,shift 项分别将您带到状态 6 和 7。但是那里没有不兼容,因为您正在尝试将状态 2 和 6 合并在一起,并将状态 3 和 7 合并在一起。换句话说,是的,这些转变 目前 说要去不同的地方,但是在你将所有具有相同核心的状态折叠在一起之后,你最终会看到他们都说要去到同一个地方。
更一般地说,我认为合并两个 LR(1) 状态不会引入 "shift/shift" 冲突。要了解这是为什么,请注意每个 LR(1) 状态都对应于 LR(0) 解析器中的一个状态,除了每个 LR 项目都已使用一组先行项目进行注释。在从 LR(1) 到 LALR(1) 的过程中,您将 LR(1) 状态与忽略先行的相同项目组合在一起,这意味着您实质上是将对应于相同 LR( 0) 状态。
因此,如果您有一个表示 "go to state T on symbol a" 的 LR(1) 状态 S,则对应于 S 的 LR(0) 状态会转换为对应于 T 的 LR(0) 状态,对于 S 将与之合并的任何 LR(1) 状态也是如此。
在构建 LALR(1) 解析器的过程中可能出现的唯一冲突是 reduce/reduce 冲突,这是您需要警惕的。
好的,我的解析器生成器之旅继续进行。这次弄到一个经典文法,据说是LALR文法:
start -> a a
a -> "A" a
a -> "B"
当我把它放入我的解析器生成器时,它给我输出:
LIST OF STATES:
-----------------------
<0: S' -> . start , $
start -> . a a , $
a -> . "A" a , "A" / "B"
a -> . "B" , "A" / "B"
{NTerm(start): 1, Term(A): 2, Term(B): 3, NTerm(a): 4}>(3104365621624877555)
--------------------
<1: S' -> start . , $
{}>(3969511602615904846)
--------------------
<2: a -> "A" . a , "A" / "B"
a -> . "A" a , "A" / "B"
a -> . "B" , "A" / "B"
{Term(A): 2, Term(B): 3, NTerm(a): 5}>(5490562805113673592)
--------------------
<3: a -> "B" . , "A" / "B"
{}>(-4845209343945471034)
--------------------
<4: start -> a . a , $
a -> . "A" a , $
a -> . "B" , $
{Term(A): 6, Term(B): 7, NTerm(a): 8}>(598157158659875896)
--------------------
<5: a -> "A" a . , "A" / "B"
{}>(436327415052220213)
--------------------
<6: a -> "A" . a , $
a -> . "A" a , $
a -> . "B" , $
{Term(A): 6, Term(B): 7, NTerm(a): 9}>(5490562805113673592)
--------------------
<7: a -> "B" . , $
{}>(-4845209343945471034)
--------------------
<8: start -> a a . , $
{}>(5795088700656730485)
--------------------
<9: a -> "A" a . , $
{}>(436327415052220213)
POSSIBLE STATES TO JOIN: (2, 6), (3, 7), (5, 9)
ATTEMPTING CONVERSION TO LALR GRAMMAR...FAILED
CONTINUING WITH CLR(1)...
这些状态与我在其他来源中读到的有关编译 LALR 语法的内容相匹配——这一步看起来不错,它会产生正确的状态,就像我手动完成一样。生成器建议——这也是其他来源所说的关于将 CLR(1) 语法转换为 LALR 的说法——指出 (2,6)
、(3,7)
、(5,9)
可以连接,但它不能这样做所以。
当我查看生成的 action 和 goto 表时,我明白了原因:
如您所见,状态 2 和 6 无法合并,因为存在不兼容的项 s2 <> s6
、s3 <> s7
等等。
但最让我惊讶的是生成器完成了它的工作并生成了一个 运行ning 程序。当我运行这个程序测试数据时,它接受了数据!因此,我的生成器生成了正确的表格。
这是否意味着这种经典的 "LALR" 语法只有在人工手动编译时才是 LALR?我的解析器生成器有什么不同之处?
我不知道您的解析器生成器是做什么的,但标准解析器生成器可以处理该语法。例如,这里是野牛的状态转换 table,生成方式:
bison --report-file=aa.output --report=all --graph=aa.dot --output=/dev/null \
<(printf "%%%%\n%s" "start: a a; a: 'A' a | 'B'")
dot -o aa.png -Tpng aa.dot
(Bison 是一个非常方便的工具;即使您正在编写自己的解析器生成器,您也可以利用它的功能。另请参阅它的 XML 输出。)
这里是稍微编辑过的报告文件(我删除了终端和非终端列表,以及一些空行,以减少使用space。)
Grammar
0 $accept: start $end
1 start: a a
2 a: 'A' a
3 | 'B'
State 0
0 $accept: . start $end
1 start: . a a
2 a: . 'A' a
3 | . 'B'
'A' shift, and go to state 1
'B' shift, and go to state 2
start go to state 3
a go to state 4
State 1
2 a: . 'A' a
2 | 'A' . a
3 | . 'B'
'A' shift, and go to state 1
'B' shift, and go to state 2
a go to state 5
State 2
3 a: 'B' .
$default reduce using rule 3 (a)
State 3
0 $accept: start . $end
$end shift, and go to state 6
State 4
1 start: a . a
2 a: . 'A' a
3 | . 'B'
'A' shift, and go to state 1
'B' shift, and go to state 2
a go to state 7
State 5
2 a: 'A' a .
$default reduce using rule 2 (a)
State 6
0 $accept: start $end .
$default accept
State 7
1 start: a a .
$default reduce using rule 1 (start)
我认为问题出在这里:
As you can see states 2 and 6 can't be joined, because there are incompatible items s2 <> s6, s3 <> s7 and so on.
这实际上不太正确。请注意,在状态 2 中,shift 项分别将您带到状态 2 和 3。在状态 6 中,shift 项分别将您带到状态 6 和 7。但是那里没有不兼容,因为您正在尝试将状态 2 和 6 合并在一起,并将状态 3 和 7 合并在一起。换句话说,是的,这些转变 目前 说要去不同的地方,但是在你将所有具有相同核心的状态折叠在一起之后,你最终会看到他们都说要去到同一个地方。
更一般地说,我认为合并两个 LR(1) 状态不会引入 "shift/shift" 冲突。要了解这是为什么,请注意每个 LR(1) 状态都对应于 LR(0) 解析器中的一个状态,除了每个 LR 项目都已使用一组先行项目进行注释。在从 LR(1) 到 LALR(1) 的过程中,您将 LR(1) 状态与忽略先行的相同项目组合在一起,这意味着您实质上是将对应于相同 LR( 0) 状态。
因此,如果您有一个表示 "go to state T on symbol a" 的 LR(1) 状态 S,则对应于 S 的 LR(0) 状态会转换为对应于 T 的 LR(0) 状态,对于 S 将与之合并的任何 LR(1) 状态也是如此。
在构建 LALR(1) 解析器的过程中可能出现的唯一冲突是 reduce/reduce 冲突,这是您需要警惕的。