我正在编写一个解析器来解析类似 C 的语法。
首先,它现在可以解析如下代码:
a = 1;
b = 2;
现在我想将行尾的分号设为可选。
最初的 YACC 规则是:
stmt: expr ';' { ... }
新行由我自己编写的词法分析器处理(代码被简化):
rule(/\r\n|\r|\n/) { increase_lineno(); return :PASS }
这里的 :PASS 指令相当于在 LEX 中不返回任何内容,它会删除当前匹配的文本并跳到下一条规则,就像通常对空格所做的那样。
因此,我不能简单地将我的 YACC 规则更改为:
stmt: expr end_of_stmt { ... }
;
end_of_stmt: ';'
| '\n'
;
所以我选择相应地由解析器动态改变词法分析器的状态。
像这样:
stmt: expr { state = :STATEMENT_END } ';' { ... }
并添加一个可以将新行与新状态匹配的词法分析器规则:
rule(/\r\n|\r|\n/, :STATEMENT_END) { increase_lineno(); state = nil; return ';' }
这意味着当词法分析器处于 :STATEMENT_END 状态时。它会像往常一样首先增加行号,然后将状态设置为初始状态,然后假装自己是一个分号。
奇怪的是它实际上不适用于以下代码:
a = 1
b = 2
我调试它并得到它实际上并没有得到';' 正如预期的那样,当扫描数字 1 之后的换行符时,状态指定的规则并没有真正执行。
并且设置新状态的代码在它已经扫描新行并且没有返回任何内容之后执行,这意味着这些工作按以下顺序完成:
- 扫描
a
,=
和1
- 扫描换行符并跳过,所以得到下一个值
b
- 插入的代码(
{ state = :STATEMENT_END }
)被执行 b
引发错误——这里出乎意料
这是我所期望的:
- 扫描
a
,=
和1
- 发现它符合规则
expr
,所以归约为stmt
- 执行插入的代码以设置新的词法分析器状态
- 扫描换行符并
;
根据新的状态匹配规则返回a - 继续扫描并解析以下行
经过反省,我发现这可能是由于 YACC 使用 LALR(1) 导致的,该解析器将首先向前读取一个令牌。当它扫描到那里时,状态尚未设置,因此无法获得正确的令牌。
我的问题是:如何让它按预期工作?我对此一无所知。
谢谢。