Parsing 重复语法分析S->;(E';';)+;

Parsing 重复语法分析S->;(E';';)+;,parsing,grammar,parser-generator,lr,Parsing,Grammar,Parser Generator,Lr,我知道如何解析如下语法: block: %empty | declaration ';' block { $3.set_scope(new Scope($1)); $3.prepend($1.initialiser()); } | statement ';' block { $3.prepend($1); } E->E'*'E E->E'+'E E->N N->“0” N->“1” 但是如果我有下面的示例语

我知道如何解析如下语法:

block: %empty
     | declaration ';' block { $3.set_scope(new Scope($1));
                               $3.prepend($1.initialiser()); }
     | statement ';' block   { $3.prepend($1); }
E->E'*'E
E->E'+'E
E->N
N->“0”
N->“1”
但是如果我有下面的示例语法(带有“regex repitition”):

E->“E”
S->(E';')+/'+'像在正则表达式中一样使用
如何使用LR(k)(或其他解析算法)解析此类语法并构建树?

这是

S → E ';'
S → S E ';'

在你的第一个片段中,你说你“知道如何解析”,你的语法是模棱两可的。我假定您正在使用某种外部优先级/关联性声明对其进行解析,因为在不指定解析树的构造方式的情况下,解析不能有意义地应用于文本,而只能用于简单识别

我在这里提供的语法并不含糊,因此体现了列表的关联性。在许多情况下,列表的关联性是不相关的,因为期望的结果只是一个列表;在这种情况下,你选择上面哪一个选项(语法上)并不重要

通常,当使用LR解析器生成器时,我们将选择左关联生成器,这是上面的第一个。这是因为正确的关联性要求在解析器堆栈上保留单个元素,直到最后可以从头到尾构造列表为止。因此,解析一个长列表可能会占用很多解析器堆栈;如果解析器生成器限制堆栈的大小,那么这最终将成为一个问题

对于新手来说,从后到前的列表构建也可能会令人困惑。一个常见的混淆(从上面的问题判断)来自以下“调试”代码(用yacc/bison语法):(为了简单起见,我实现了
(E';)*
,而不是
(E';)+
;大多数时候,这正是您想要的。)

这将导致列表中的元素从右到左打印出来,如果您希望代码这样做的话,这是很好的。但它通常会导致混乱,这对调试代码有点不利。(这就是为什么我总是建议使用内置于解析器生成器中的调试工具的原因之一,并且总是选择内置有调试工具的解析器生成器,而不是通过一组特殊的
print
语句随意创建解析器跟踪。)

例如,如果解析器是即时计算器的一部分,那么背对背的计算显然是一个巨大的错误。您希望计算表达式,然后一次丢弃一个表达式(从左到右,左关联性是必须的)

但情况并非总是如此。假设我们是为了构造一个AST(或其他导致代码生成的中间产品)而进行解析的。假设这里的元素是语句,而列表表示一个块(减去块分隔符,它将附加在一些外部产品中)。在一种语言中,块中的声明是块的局部声明,并且范围从声明到块的末尾,程序的语义强烈建议正确的关联性。考虑下面的一些愚蠢的例子:

1    function example(i: integer)
2       var tmp: integer = i;
3       var i: integer = tmp - 1;
4       return tmp * i;
5    end
这里,
tmp
的范围从语句2一直延伸到语句4的末尾。参数列表中
i
的范围从语句1扩展到语句5,但在语句3中,它被另一个同名变量的声明所掩盖,该变量的范围从语句3扩展到语句4的末尾

为了有意义地解析这种语言,我们希望在每次看到声明时都创建一个新的子作用域,并将该子作用域附加到声明之后启动的程序部分。这就意味着类似这样的语法:

block: %empty
     | declaration ';' block { $3.set_scope(new Scope($1));
                               $3.prepend($1.initialiser()); }
     | statement ';' block   { $3.prepend($1); }

只是想说清楚:有一些众所周知的转换习惯用法

S → A B*

与上下文无关的语法。第一个是

S: A
 | S B
第二个是

S: A
 | B S

如果
A
B
(换句话说,如果您想要
S→ B+
,表示与
S中任一个相同的文本→ B B*
S→ B*B
,你可以使用
S:B | S B
S:B | B S
。我没有这样做,因为这涉及到重复
B
以及与之对应的任何动作,如果
B
不是一个符号,这很烦人。重复
B
(或创建一个中间非终端来表示它,如果它确实很复杂或具有复杂的操作),但避免这个问题更简单。

谢谢你,这帮了大忙。这意味着解析
S->ae*
的唯一方法是创建一个附加规则:
S->aa
a->E
a->%empty
@Hexception:不,你为什么需要一个附加规则?
S->a->S->se
可以(只要你擅长左联想)。类似地,你可以用正确的关联性解析
S->E*a
。这东西可能比你想象的要简单。试着在一张纸上进行推导,看看上面的工作原理。推导只是一个有符号的小游戏,你可以用一个可能的替换符号替换一个符号后果。
S: A
 | S B
S: A
 | B S