转换/减少与中缀部分的冲突

时间:2015-03-16 01:25:17

标签: yacc infix-notation lalr shift-reduce-conflict ocamlyacc

我遇到了类似yacc的实现(特别是使用ocamlyacc)的语法错误,包括普通的中缀操作和中缀部分,就像在Haskell中一样。我希望所有这些都是语法上的:

(+1)
(1+)
(+)
(1+1)

然而,即使通过摆弄关联性/优先级声明,我也无法实现这一点。我可以在grammar.output中看到问题发生的地方(它会在我希望它减少的地方转移),但我还没有能够按照我想要的方式哄它。以下是该问题的简化演示。

lex.mll有:

{
  open Parse
  exception Eof
}
rule token = parse
  | [' ' '\t'] { token lexbuf }
  | ['\n'] { EOL }
  | ['0'-'9']+ as num {INT(int_of_string num)}
  | '+' { PLUS }
  | '*' { TIMES }
  | '(' { LPAREN }
  | ')' { RPAREN }
  | eof { raise Eof }

main.ml有:

let _ =
  try
    let lexbuf = Lexing.from_channel stdin in
    while true do
      let result = Parse.start Lex.token lexbuf in
      print_string result; print_newline(); flush stdout
    done
  with Lex.Eof -> exit 0

和parse.mly(麻烦所在)有:

%token <int> INT
%token PLUS TIMES
%token LPAREN RPAREN
%token EOL

%left PLUS
%left TIMES

%start start
%type <string> start
%%

start:
| expr EOL {$1}
;

expr:
| application {$1}
| expr PLUS expr {"[" ^ $1 ^ "+" ^ $3 ^"]"}
| expr TIMES expr {"[" ^ $1 ^ "*" ^ $3 ^"]"}
;

section:
| LPAREN atom PLUS RPAREN { "(" ^ $2 ^ " +)" }
| LPAREN PLUS atom RPAREN { "(+ " ^ $3 ^ ")" }
| LPAREN PLUS RPAREN { "(+)" }
;

application:
| atom {$1}
| application atom {"[" ^ $1 ^ " " ^ $2 ^ "]"}
;

atom:
| INT {string_of_int $1}
| section { $1 }
| LPAREN expr RPAREN { "(" ^ $2 ^ ")" }
;

%%

正在运行ocamlyacc告诉我有1 shift/reduce conflict。特别是这里是详细日志的相关部分:

Rules:
   6  section : LPAREN atom PLUS RPAREN
   ...
   9  application : atom
...
12: shift/reduce conflict (shift 21, reduce 9) on PLUS
state 12
        section : LPAREN atom . PLUS RPAREN  (6)
        application : atom .  (9)

        PLUS  shift 21
        INT  reduce 9
        MINUS  reduce 9
        TIMES  reduce 9
        LPAREN  reduce 9
        RPAREN  reduce 9
...
state 21
        section : LPAREN atom PLUS . RPAREN  (6)

        RPAREN  shift 26
        .  error

运行已编译的程序将正确解析以下所有内容:

(1+)
(+1)
(+)
1+2

但失败了:

(1+2)

另一方面,如果我创建一个具有高优先级的虚拟标记HIGH

%left PLUS MINUS
%left TIMES
%nonassoc HIGH

然后将%prec HIGH放在规则9上:

application: atom %prec HIGH {$1}

在这种情况下(1+2)将进行解析,但(1+)赢了。

我理解转移/减少冲突的一般背景。我无法弄清楚如何协商解决这个解析挑战。

1 个答案:

答案 0 :(得分:1)

留出很多语法,你有以下作品,所有这些都可以同时使用。

atom:    LPAREN expr RPAREN
expr:           expr PLUS expr
section: LPAREN atom PLUS RPAREN

所以我们假设我们刚读过 0 - 即LPARENINT - 以及下一个token up是 + 。此时,我们需要将INT缩减为atom,但我们无法确定后面的内容是否与{{1}相匹配或atom规则。要匹配section规则,我们需要将atom缩减为atom - 通过expr - - 但为了匹配application规则,我们需要将其保留为section。因此我们有转换/减少冲突;我们不知道是否需要转移 + < / kbd>现在,或者在减少一些单位之后。

简单的解决方案是推迟决定。如果atom规则是:

section
那么就没有问题了。我们继续减少单位直到我们得到section: LPAREN expr PLUS RPAREN ,然后我们移动 + ,然后我们要么看到或者我们我会看到可以开始expr的事情。冲突得到解决。

当然,这改变了语言,使其更加宽容。我们可能不想接受:

expr

( 3 + 4 + )

但结果语法并不含糊。我们可以让解析器继续,然后在减少( (+) 3 4 + ) 时发出错误消息,方法是检查section是否被适当限制。 (这是一种非常常见的技术,并没有任何问题。)

或者,我们可以分开

$2

统治为两个相互排斥的替代方案:

expr: expr PLUS expr

这也可以解决冲突,因为expr: atom PLUS expr expr: expr_not_an_atom PLUS expr 无法缩减为atom。但它留下了如何定义expr_not_an_atom的问题。

碰巧的是,我很确定这是可能的,但这并不是一件轻而易举的事,其后果将会影响语法。我也不能给你一个算法,因为CFG - 不像正则表达式 - 在否定或设置差异时不会关闭。但基本上,你需要通过非终端级联,将它们分开,以便每个备选方案都适合expr_not_an_atomatom - 这也是一种合法的方法,但结果语法可能很难读取。

如果您使用的是expr_not_an_atom,那么您将有另一种选择:生成GLR语法。只要你的语言不明确,GLR语法就会找到正确的解析,可能会稍微慢一些,但你的工作量会少很多。

如果它有帮助,here's a slightly related answer我在其中制作了一个完全解决的分裂非终端解决方案。