Parsing 针对不明确的lambda语法的yacc-shift-reduce

Parsing 针对不明确的lambda语法的yacc-shift-reduce,parsing,go,yacc,shift-reduce-conflict,Parsing,Go,Yacc,Shift Reduce Conflict,我正在用Yacc为一种玩具语言编写语法(与Go打包的一种),由于以下伪问题,我有一个预期的移位减少冲突。我必须把这道语法题归结为以下几点 start: stmt_list expr: INT | IDENT | lambda | '(' expr ')' { $$ = $2 } lambda: '(' params ')' '{' stmt_list '}' params: expr | params ',' expr stmt: /* empty */ | expr

我正在用Yacc为一种玩具语言编写语法(与Go打包的一种),由于以下伪问题,我有一个预期的移位减少冲突。我必须把这道语法题归结为以下几点

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 }
(这将是identity函数的lambda,返回其输入)。我得到:

语法错误:意外的“{”

这是因为当<代码>(a)< /代码>被读取时,解析器选择将它作为“代码>”(“ExPR”)“< /代码>,而不是考虑它是“代码>”(“PARAMS”)。鉴于此冲突是一个移位减少而不是减少减少,我假设这是可解的。我只是不知道如何构造语法来支持这种语法。< /P> 编辑|这很难看,但我正在考虑定义一个标记,以便lexer能够识别“'''{”序列并将其作为单个标记发送以解决此问题


编辑2 |实际上,更好的是,我将使lambda需要像
->(a,b){a*b}这样的语法语法中的
,但是让lexer发出
->
,而不是在实际的源代码中。

您的分析确实是正确的;虽然语法并不含糊不清,但解析器不可能在输入减少到
和lookahead
)的情况下做出决定
在移动
之前,是否应将
expr
减少为
参数
,或者是否应将
作为
lambda的一部分进行移动。如果下一个标记可见,则可以做出决定,因此语法LR(2)不属于go/yacc的权限范围

如果您使用的是bison,那么可以通过请求GLR解析器轻松解决这个问题,但我不相信go/yacc提供了这个特性

这种语言有一个LR(1)语法(对于
k
的任何值,总是有一个LR(1)语法对应于任何LR(k)语法),但是手工书写相当烦人。LR(k)到LR(1)的基本思想转换是通过将上下文的k-1标记累积到每个产品中来向前移动k-1标记。因此,在
k
为2的情况下,每个产品P:
N→ α
将替换为产品
TNU→ TαU
对于
FIRST(α)
中的每个
T
,以及
FOLLOW(N)
中的每个
U
[见注1],这导致任何非平凡语法中的非终结符大量膨胀

与其追求这个想法,不如让我提出两个更简单的解决方案,这两个方案你们似乎都非常接近

首先,在您介绍的语法中,问题实际上只是当两个令牌为{时需要两个令牌的前瞻性。这在lexer中很容易被检测到,并导致一个解决方案,该解决方案仍然是有缺陷的,但更简单:Return
){
作为单个标记。您需要处理中间的空格等,但不需要在lexer中保留任何上下文。这增加了一个额外的好处,即您不需要将
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
笔记
  • 这只是一个概要;完整的算法包括恢复原始解析树的机制。如果
    k
    大于2,则
    T
    U
    FIRSTk-1
    FOLLOWk-1
    集合的字符串

  • 如果它确实是一个移位-减少冲突,并且您只需要移位行为,那么解析器生成器可能会为您提供一种更喜欢移位而不是减少的方法。这是解决“If-then stmt”和“If-then stmt else stmt”语法规则冲突的经典方法,而If语句也可以是一个语句

    您可以通过两种方式获得此效果: a) 依靠解析引擎的意外行为。 如果LALR解析器首先处理移位,如果没有移位,则减少移位,那么您将免费获得此“首选移位”。解析器生成器所要做的就是构建解析表,即使检测到冲突。 b) 强制执行意外行为。设计(或获取)解析器生成器以接受“令牌T上的首选移位”。然后可以消除歧义。仍然需要实现解析引擎(如中所示),但这非常简单

    我认为这比滥用lexer来制作奇怪的标记更容易/更干净(而且这并不总是有效)

    显然,您可以优先选择reduces以将其转换为另一种方式。通过一些额外的攻击,您可以使shift vs reduce特定于冲突发生的状态;您甚至可以使其特定于一对冲突规则,但现在解析引擎需要保留非终端的首选项数据。这仍然是个问题不难。最后,您可以为每个非终结符添加一个谓词,当shift-reduce冲突即将发生时调用该谓词,并让它提供决策


    关键是你不必接受“纯”LALR解析;如果您愿意稍微修改一下解析器生成器/引擎,您可以通过多种方式轻松地弯曲它。这为理解这些工具的工作原理提供了一个很好的理由;然后您可以滥用它们为您带来好处。

    语句不是用标点符号分隔的吗?IOW,if
    (v){v*2}
    不是lambda,它是什么?一个
    stmt\u列表
    或其他什么?请添加一点mor