Parsing 解析器:在类似JavaScript的语法中区分括号和函数声明

Parsing 解析器:在类似JavaScript的语法中区分括号和函数声明,parsing,ocaml,grammar,menhir,Parsing,Ocaml,Grammar,Menhir,我正在开发一个使用OCaml和Menhir作为解析器和词法分析器的编译器。当我编写类似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 =

我正在开发一个使用OCaml和Menhir作为解析器和词法分析器的编译器。当我编写类似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) }
    ...
但编译时会出现reduce/reduce错误:

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
(一个arg函数)。但我仍然无法理解为什么它仍然会产生这个错误,因为我非常清楚,括号后面跟一个粗箭头将是一个函数

有人能帮我一点忙吗

但我仍然无法理解为什么它仍然会产生这个错误,因为我非常清楚,括号后面跟一个粗箭头将是一个函数

是的,非常清晰。语法是完全明确的。但您并不局限于一次查看一个输入标记,而LR(1)解析器是。当解析器试图决定如何处理
(a)
中的
a
时,它还看不到胖箭头,因此必须先做出决定。也就是说,在使用之前,解析器需要确定它前面的是
expr
还是
分隔的非空列表

可能值得注意的是,语法实际上是LR(2):再加上一个前瞻标记,冲突就可以解决了。这并不是什么安慰,因为Menhir没有提供任何增加前瞻性的机制,但它确实意味着存在一个解决方案,因为一种语言的LR(k)语法的存在意味着同一种语言的LR(1)语法的存在;甚至有一个机械算法来产生LR(1)语法

与其转换整个语法,唯一稍微混乱的解决办法是分离“(a)`格,这可以通过一对明显冗余的规则来完成:

expr: LPAREN ID RPAREN ARROW expr
    | LPAREN ID RPAREN
第二个产品显然与
LPAREN expr RPAREN
冲突,但这是一个shift/reduce冲突,而不是reduce/reduce冲突,可以通过赋予
RPAREN
ID
更高的优先级来解决,以强制解决有利于移位
RPAREN

这完全是对优先级声明的曲解,随着语法变得更加复杂,这很可能会成为问题。一个理论上更合理的解决方案是定义
expr\u而不是标识符。你可以找到这样一个例子,这是一个非常相似的语法,在其他一些类似问题的答案

特别是,Yacc自己的语法是LR(2)(在看到启动下一个规则的非终结符后面的
之前,您无法判断规则是否已结束)。对于这种语法也存在类似的解决方案,但是大多数类似于yacc的解析器生成器通过将额外的前瞻功能推入lexer分析器来解决这个问题。例如,您可以将
)=>
识别为单个标记(包括内部空白),或者如果下一个标记是胖箭头,则可以发出不同的右括号标记