我是一个非常初学的menhir。 我想知道如何用我自己的语言解析OCaml像tuple-pattern,这与OCaml非常相似。
例如,在表达式let a,b,c = ...
中,
a, b, c
应该被解析为Tuple (Var "a", Var "b", Var "c")
。
但是,在下面的解析器定义中,上面的示例被解析为Tuple (Tuple (Var "a", Var "b"), Var "c")
。
我想知道如何修复以下定义来解析ocaml等模式。
我已经检查了OCaml的解析器。但是我不确定如何实现这一点。 我认为我的定义类似于OCaml的一个...... 他们用什么神奇的东西?
%token LPAREN
%token RPAREN
%token EOF
%token COMMA
%left COMMA
%token <string> LIDENT
%token UNDERBAR
%nonassoc below_COMMA
%start <Token.token> toplevel
%%
toplevel:
| p = pattern EOF { p }
pattern:
| p = simple_pattern { p }
| psec = pattern_tuple %prec below_COMMA
{ Ppat_tuple (List.rev psec) }
simple_pattern:
| UNDERBAR { Ppat_any }
| LPAREN RPAREN { Ppat_unit }
| v = lident { Ppat_var v }
| LPAREN p = pattern RPAREN { p }
pattern_tuple:
| seq = pattern_tuple; COMMA; p = pattern { p :: seq }
| p1 = pattern; COMMA; p2 = pattern { [p2; p1] }
lident:
| l = LIDENT { Pident l }
结果如下:
[~/ocaml/error] menhir --interpret --interpret-show-cst ./parser.mly
File "./parser.mly", line 27, characters 2-42:
Warning: production pattern_tuple -> pattern_tuple COMMA pattern is never reduced.
Warning: in total, 1 productions are never reduced.
LIDENT COMMA LIDENT COMMA LIDENT
ACCEPT
[toplevel:
[pattern:
[pattern_tuple:
[pattern:
[pattern_tuple:
[pattern: [simple_pattern: [lident: LIDENT]]]
COMMA
[pattern: [simple_pattern: [lident: LIDENT]]]
]
]
COMMA
[pattern: [simple_pattern: [lident: LIDENT]]]
]
]
EOF
]
答案 0 :(得分:4)
它包含典型的shift-reduce冲突,并且您通过指定优先级在其解析中犯了一个错误。请打开任何有关Yacc解析的书籍,并检查shift-reduce冲突及其解决方案。
让我们使用您的规则来看待它。假设我们有以下输入,并且解析器正在展望第二个,
:
( p1 , p2 , ...
↑
Yacc is looking at this second COMMA token
他们有两种可能性:
p1 , p2
作为pattern
。它使用pattern
COMMA
并向前移动向前光标以尝试使逗号分隔列表更长。您可以轻松地从%prec below_COMMA
规则中删除pattern
冲突:
$ menhir z.mly # the %prec thing is removed
...
File "z.mly", line 4, characters 0-9:
Warning: the precedence level assigned to below_COMMA is never useful.
Warning: one state has shift/reduce conflicts.
Warning: one shift/reduce conflict was arbitrarily resolved.
许多Yacc文件说在这种情况下Yacc更喜欢轮班,这种默认通常与人的意图相符,包括你的情况。因此,其中一个解决方案就是放弃%prec below_COMMA
事情并忘记警告。
如果您不喜欢使用shift减少冲突警告(这就是灵魂!),您可以明确说明在这种情况下应该使用优先级选择哪个规则,就像OCaml&#39; s {{1} }。 (顺便说一下,OCaml&#39; s parser.mly
是一个减少分数的珠宝盒。如果你不熟悉,你应该检查其中的一个或两个。)
Yacc在轮班减少冲突时选择优先级更高的规则。对于shift,它的优先级是前瞻光标处的一个标记,在这种情况下为parser.mly
。 reduce的优先级可以通过相应规则的COMMA
后缀来声明。如果你没有指定它,我猜一个规则的优先级是未定义的,这就是为什么如果你删除%prec TOKEN
就会发出shift减少冲突的原因。
现在问题是:哪个优先级更高,%prec below_COMMA
或COMMA
?这应该在below_COMMA
文件的前导码中声明。 (这就是为什么我要求提问者展示那部分。)
mly
我跳过...
%left COMMA
...
%nonassoc below_COMMA
和%left
的意思,因为所有的Yacc书都应该解释它们。此处%nonassoc
伪令牌位于 below_COMMA
下方。这意味着COMMA
的优先级高于<{1}} 更高。因此,上面的示例选择Reduce,并且针对意图得到below_COMMA
。
正确的优先权声明恰恰相反。要让Shift发生,COMMA
必须<{>> <{1}} 优先级
( (p1, p2), ...
请参阅OCaml&#39; below_COMMA
。它确实是这样的。把&#34;放在&#34;以上&#34;声音完全疯狂,但这不是Menhir的错。这是Yacc不幸的传统。怪它。 OCaml&#39; COMMA
已发表评论:
...
%nonassoc below_COMMA
%left COMMA
...
答案 1 :(得分:3)
这是我重写它的方式:
%token LPAREN %token RPAREN %token EOF %token COMMA %token LIDENT %token UNDERBAR %start toplevel %% toplevel: | p = pattern EOF { p } pattern: | p = simple_pattern; tail = pattern_tuple_tail { match tail with | [] -> p | _ -> Ppat_tuple (p :: tail) } pattern_tuple_tail: | COMMA; p = simple_pattern; seq = pattern_tuple_tail { p :: seq } | { [] } simple_pattern: | UNDERBAR { Ppat_any } | LPAREN RPAREN { Ppat_unit } | v = lident { Ppat_var v } | LPAREN p = pattern RPAREN { p } lident: | l = LIDENT { Pident l }
一个重大变化是元组元素不再是pattern
而是simple_pattern
。 pattern
本身是一个或多个元素的逗号分隔序列。
$ menhir --interpret --interpret-show-cst parser.mly LIDENT COMMA LIDENT COMMA LIDENT ACCEPT [toplevel: [pattern: [simple_pattern: [lident: LIDENT]] [pattern_tuple_tail: COMMA [simple_pattern: [lident: LIDENT]] [pattern_tuple_tail: COMMA [simple_pattern: [lident: LIDENT]] [pattern_tuple_tail:] ] ] ] EOF ]