我正在为Yacc(与Go一起打包)编写玩具语言的语法,由于以下伪问题,我有一个预期的shift-reduce冲突。我必须将问题语法简化为以下内容。
start:
stmt_list
expr:
INT | IDENT | lambda | '(' expr ')' { $$ = $2 }
lambda:
'(' params ')' '{' stmt_list '}'
params:
expr | params ',' expr
stmt:
/* empty */ | expr
stmt_list:
stmt | stmt_list ';' stmt
lambda函数看起来像这样:
map((v) { v * 2 }, collection)
我的解析器发出:
冲突:1班/减少
鉴于输入:
(a)
它正确地按expr
规则解析'(' expr ')'
。但是输入了:
(a) { a }
(这将是身份函数的lambda,返回其输入)。我明白了:
语法错误:意外的'{'
这是因为当读取(a)
时,解析器选择将其缩减为'(' expr ')'
,而不是将其视为'(' params ')'
。鉴于这种冲突是转移减少而不是减少减少,我假设这是可以解决的。我只是不知道如何构造语法来支持这种语法。
编辑|这很丑陋,但我正在考虑定义一个令牌,以便词法分析器可以识别')''''序列并将其作为单个令牌发送以解决此问题。
编辑2 |实际上,更好的是,我会在语法中使lambdas需要->(a, b) { a * b}
之类的语法,但让词法分析器发出->
而不是实际的源代码。
答案 0 :(得分:3)
你的分析确实是正确的;虽然语法不明确,但是解析器不可能将输入减少到( <expr>
并且使用预测)
来判断expr
是否应该减少到params
在转移)
之前,或者)
是否应该作为lambda
的一部分进行转移。如果下一个标记可见,则可以做出决定,因此语法LR(2),超出了go / yacc的权限。
如果您使用的是野牛,您可以通过申请GLR解析器轻松解决此问题,但我不相信go / yacc提供该功能。
语言有一个LR(1)语法(对于k
的任何值,总有一个LR(1)语法对应于任何LR(k)语法,但写入时却相当烦人手。 LR(k)到LR(1)变换的基本思想是通过将k-1个上下文标记累积到每个生成中来向前移动缩减决策k-1 tokens。因此,如果k
为2,则每个生产P:N → α
将替换为TNU → Tα U
中每个T
的作品FIRST(α)
和每个{{1}在U
中。 [见注1]这会导致任何非平凡语法中非终端的大量爆发。
让我提出两个更简单的解决方案,而不是追求这个想法,这两个解决方案似乎都非常接近。
首先,在你提出的语法中,当两个令牌是) {时,问题实际上只是需要一个双令牌前瞻。这可以很容易地在词法分析器中被检测到,并且导致一个仍然很简单但是更简单的黑客的解决方案:将FOLLOW(N)
作为单个令牌返回。您需要处理插入的空格等,但它并不需要在词法分析器中保留任何上下文。这有额外的好处,您不需要将){
定义为params
的列表;它们只能是expr
的列表(如果相关的话;评论表明它不是'。
我认为更清洁的替代方案是扩展您已经提出的解决方案:接受一点点过多并拒绝语义操作中的错误。在这种情况下,您可能会执行以下操作:
IDENT
start:
stmt_list
expr:
INT
| IDENT
| lambda
| '(' expr_list ')'
{ // If $2 has more than one expr, report error
$$ = $2
}
lambda:
'(' expr_list ')' '{' stmt_list '}'
{ // If anything in expr_list is not a valid param, report error
$$ = make_lambda($2, $4)
}
expr_list:
expr | expr_list ',' expr
stmt:
/* empty */ | expr
stmt_list:
stmt | stmt_list ';' stmt
大于2,那么k
和T
就是U
和FIRSTk-1
设置的字符串。答案 1 :(得分:0)
如果它确实是一个shift-reduce冲突,并且你只想要转换行为,那么你的解析器生成器可能会为你提供一种更喜欢shift和reduce的方法。这通常是当if语句也可以是语句时,如何解决“if-then-stmt”和“if-then-stmt-else-stmt”的语法规则的冲突。
请参阅http://www.gnu.org/software/bison/manual/html_node/Shift_002fReduce.html
你可以通过两种方式获得这种效果: a)依靠解析引擎的意外行为。 如果LALR解析器首先处理移位,然后在没有移位的情况下进行缩减,那么您将免费获得此“首选移位”。无论如何,即使存在检测到的冲突,所有解析器生成器都要构建解析表。 b)强制执行意外行为。设计(或获取)解析器生成器接受“更喜欢转移令牌T”。然后人们可以抑制歧义。仍然需要像a)中那样实现解析引擎,但这很容易。
我认为这比滥用词法分析器制作奇怪的令牌更容易/更清洁(但这并不总是有用)。
显然,您可以优先考虑减少以反转它。通过一些额外的黑客攻击,您可以根据发生冲突的状态进行shift-vs-reduce;你甚至可以使它特定于一对冲突的规则,但现在解析引擎需要保留优先级的优先级数据。那仍然不难。最后,您可以为每个非终结符添加一个谓词,该谓词在即将发生shift-reduce冲突时被调用,并且它可以提供决策。
关键是你不必接受“纯粹的”LALR解析;如果你愿意稍微修改解析器生成器/引擎,你可以通过各种方式轻松地弯曲它。这给了一个很好的理由来理解这些工具是如何工作的;然后你可以滥用它们为你的利益。