我正在使用OCaml和Menhir作为解析器和词法分析器的编译器。当我编写像Grammar这样的JavaScript时,同时使用诸如lambda函数定义之类的(a, b) => a + b
和带有括号的子表达式优先级的算术(a + b) * c
时,我会写
expr
: x = ID { EId(x) }
...
| LPAREN; e = expr; RPAREN; { e }
...
| LPAREN; args = separated_list(COMMA, ID); RPAREN; ARROW; body = expr; { EFunction(args, body) }
在parser.mly
中。
只需添加更多上下文,我的lexer.mll
就这样:
let letter = ['a'-'z' 'A'-'Z']
let lud = ['a'-'z' 'A'-'Z' '_' '0'-'9']
let id = letter lud*
rule read = parse
...
| "(" { LPAREN }
| ")" { RPAREN }
| "," { COMMA }
| "=>" { ARROW }
...
| id { ID (Lexing.lexeme lexbuf) }
...
但是编译时会得到减少/减少错误:
Error: do not know how to resolve a reduce/reduce conflict between the following two productions:
expr -> ID
separated_nonempty_list(COMMA,ID) -> ID
我知道问题可能是由以下两者之间的模棱两可引起的:(a)
和(a) => a
(one-arg-function)。但是我仍然不明白为什么它仍然会产生此错误,因为对我来说,非常清楚的是括号后面是粗箭头=>
将是一个函数...
有人可以帮我吗?
答案 0 :(得分:1)
但是我仍然不明白为什么它仍然会产生此错误,因为对我来说,非常清楚的是括号后面是粗箭头
=>
将是一个函数...
是的,这非常清楚。语法是完全明确的。但是,您不仅限于一次查看输入的一个令牌,而LR( 1 )解析器也是如此。当解析器试图决定如何处理a
中的(a)
时,它还看不到粗箭头,因此必须先做出决定。也就是说,在使用)之前,解析器需要确定在它之前是expr
还是separated_nonempty_list
。
可能值得注意的是该语法实际上是LR(2):另外一个前瞻标记,并且可以解决冲突。因为Menhir没有提供任何增加超前性的机制,所以这并不是什么安慰,但这确实意味着存在一种解决方案,因为一种语言的LR(k)语法的存在暗示着同一语言的LR(1)语法的存在。语言;甚至还有一种机械算法可以产生LR(1)语法。
除了转换整个语法之外,唯一的麻烦的解决方案是隔离'(a)'情况,这可以使用一对看似冗余的规则来完成:
expr: LPAREN ID RPAREN ARROW expr
| LPAREN ID RPAREN
第二个产生显然与LPAREN expr RPAREN
发生冲突,但这是移位/减少冲突而不是减少/减少冲突,可以通过赋予RPAREN
比ID
更高的优先级来解决为了强制解决方案而赞成移动RPAREN
。
这是对优先级声明的完全颠覆,随着语法变得越来越复杂,它很可能会成为问题。从理论上讲,更合理的解决方案是定义expr_other_than_identifier
。您可以找到该here的示例,它是一个非常相似的语法,并且在其他一些SO中也提供了类似问题的答案。
尤其是,Yacc自己的语法是LR(2)(在看到开始于下一条规则的非终结符之后的:
之前,您无法说出一条规则已经结束)。对于这种语法,存在类似的解决方案,但是大多数类似yacc的解析器生成器通过将额外的超前行推入词法分析器来解决该问题。例如,您可以将) =>
识别为单个标记(包括内部空格),或者如果下一个标记是粗箭头,则可以发出不同的右括号标记。