yacc shift-reduce用于模糊的lambda语法

时间:2016-09-02 23:51:59

标签: parsing go yacc shift-reduce-conflict

我正在为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}之类的语法,但让词法分析器发出->而不是实际的源代码。

2 个答案:

答案 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 → α将替换为TNUTα U中每个T的作品FIRST(α)和每个{{1}在U中。 [见注1]这会导致任何非平凡语法中非终端的大量爆发。

让我提出两个更简单的解决方案,而不是追求这个想法,这两个解决方案似乎都非常接近。

首先,在你提出的语法中,当两个令牌是 {时,问题实际上只是需要一个双令牌前瞻。这可以很容易地在词法分析器中被检测到,并且导致一个仍然很简单但是更简单的黑客的解决方案:将FOLLOW(N)作为单个令牌返回。您需要处理插入的空格等,但它并不需要在词法分析器中保留任何上下文。这有额外的好处,您不需要将){定义为params的列表;它们只能是expr的列表(如果相关的话;评论表明它不是'。

我认为更清洁的替代方案是扩展您已经提出的解决方案:接受一点点过多并拒绝语义操作中的错误。在这种情况下,您可能会执行以下操作:

IDENT

注释

  1. 那只是一个大纲;完整的算法包括恢复原始解析树的机制。如果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,那么kT就是UFIRSTk-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解析;如果你愿意稍微修改解析器生成器/引擎,你可以通过各种方式轻松地弯曲它。这给了一个很好的理由来理解这些工具是如何工作的;然后你可以滥用它们为你的利益。