Parsing 解析器:在类似JavaScript的语法中区分括号和函数声明
我正在开发一个使用OCaml和Menhir作为解析器和词法分析器的编译器。当我编写类似JavaScript的语法时,使用类似lambda函数定义的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 =
(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分析器来解决这个问题。例如,您可以将)=>
识别为单个标记(包括内部空白),或者如果下一个标记是胖箭头,则可以发出不同的右括号标记