我正在尝试使用Menhir为正则表达式的语言编写解析器。在我修改它以消除歧义之前,我想要的语法看起来有点像下面的例子。注意"排序/连接"是隐式的,没有与该操作相关联的令牌。
%token LPAREN RPAREN
%token CHAR STAR PIPE
%token EOF
%start <unit> parse
%%
parse: re EOF {()}
re:
| LPAREN re RPAREN {()} (* Grouping *)
| CHAR {()} (* Single character *)
| re STAR {()} (* Kleene star *)
| re re {()} (* Sequencing / Concatenation *)
| re PIPE re {()} (* Alternation *)
如果我有连接令牌,我可以通过使用优先级声明来消除歧义
%token LPAREN RPAREN
%token CHAR STAR PIPE
%token CONCAT
%token EOF
%left PIPE
%left CONCAT
%nonassoc STAR
%start <unit> parse
%%
parse: re EOF {()}
re:
| LPAREN re RPAREN {()} (* Grouping *)
| CHAR {()} (* Single character *)
| re STAR {()} (* Kleene star *)
| re CONCAT re {()} (* Sequencing / Concatenation *)
| re PIPE re {()} (* Alternation *)
但是,如果没有连接规则中的CONCAT令牌,我就无法工作。我尝试使用%prec
声明,但仍然存在一些转移/减少冲突。
%token LPAREN RPAREN
%token CHAR STAR PIPE
%token CONCAT
%token EOF
%left PIPE
%left CONCAT
%nonassoc STAR
%start <unit> parse
%%
parse: re EOF {()}
re:
| LPAREN re RPAREN {()} (* Grouping *)
| CHAR {()} (* Single character *)
| re STAR {()} (* Kleene star *)
| re re %prec CONCAT {()} (* Sequencing / Concatenation *)
| re PIPE re {()} (* Alternation *)
我认为这可能是因为menhir无法说明排序应该是左关联的,但我不能100%确定是否是导致问题的原因。
到目前为止,我能找到的唯一解决方案是将re
规则分解为一系列不同的规则,使优先级和关联性明确:
%token LPAREN RPAREN
%token CHAR STAR PIPE
%token EOF
%start <unit> parse
%%
parse: re EOF {()}
re: re3 {()}
re0:
| LPAREN re RPAREN {()} (* Grouping *)
| CHAR {()} (* Single character *)
re1:
| re0 {()}
| re0 STAR {()} (* Kleene star *)
re2:
| re1 {()}
| re2 re1 {()} (* Sequencing / Concatenation *)
re3:
| re2 {()}
| re3 PIPE re2 {()} (* Alternation *)
虽然这最后一个例子工作得很好,但我真的很好奇是否可以通过使用优先级和关联性声明来删除所有的歧义和冲突,而不需要重写语法。
答案 0 :(得分:1)
嗯,首先,这不是Menhir的问题,而是Menhir接受的那种语法,它位于LR(1)
集合中。如果您提供的语法不需要优先注释,则语法将被视为SLR(1)
,LR(1)
的子集。
你的上一个例子是有效的,因为你有每个优先级的产生(比如解析表达式语法,本质上是明确的),绝对不是一个糟糕的写作方式;几个现代编译器使用这种表示法来处理更复杂的语言。
要了解您的问题,我们首先要求Menhir向我们解释冲突的存在地点:
menhir --explain parser.mly
它将生成一个parser.conflicts
文件,其中包含可以采取行动,减少和转移的状态的说明:
...
** In state 8, looking ahead at STAR, shifting is permitted
** because of the following sub-derivation:
re re
re . STAR
** In state 8, looking ahead at STAR, reducing production
** re -> re re
** is permitted because of the following sub-derivation:
re STAR // lookahead token appears
re re .
** Conflict (shift/reduce) in state 7.
** Tokens involved: STAR PIPE LPAREN CHAR
** The following explanations concentrate on token STAR.
** This state is reached from parse after reading:
re PIPE re
** The derivations that appear below have the following common factor:
** (The question mark symbol (?) represents the spot where the derivations begin to differ.)
parse
re EOF
(?)
LR(1)
的语法真的很模糊,所以:
CHAR CHAR STAR
可以计算为:
(CHAR CHAR) STAR
CHAR (CHAR STAR)
另一种无冲突地重写语法的方法是通过list
进行连接:
re:
| term PIPE re
| term { } (* Left associativity *)
term:
| list(base STAR* { }) { } (* Concatenation is taken by list *)
base:
| CHAR
| LPAREN re RPAREN { }