我试图编写一个用于解析JavaScript文件的Bison C ++解析器,但我不知道如何使分号成为可选。
对于ECMAScript 2018规范(https://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf,第11.9章),分号实际上不是可选的,而是在解析过程中自动插入的。在规范中指出:
当从左到右解析源文本时,遇到一个令牌(称为有问题的令牌),该令牌是 不允许任何语法产生,然后在冒犯之前自动插入分号 满足以下一个或多个条件的令牌:
- 有问题的令牌与至少一个LineTerminator与前一个令牌分离[...]
因此,我正在尝试以一种幼稚的方式解决此问题:
error
特殊令牌检测错误; yylex
调用时返回一个新的分号标记;在随后的调用中,当发生语法错误时,它将返回以前是有问题的令牌。解析器的非常简化的结构如下:
program:
stmt_list END
;
stmt_list:
%empty
| stmt_list stmt
| stmt_list error { /* error detected; tell the lexer about the syntax error */ }
;
stmt:
value SEMICOLON
| [other types of statements...]
;
value:
NUMBER
| STRING
;
但是这样做,如果文件包含有效的JavaScript语句,但没有终止分号,但是包含换行符,则当遇到有问题的令牌时,解析器会将语句的其余部分简化为error
令牌。当我告诉词法分析器语法错误时,解析器已经将error
令牌简化为stmt_list
,并且先前的有效指令丢失了,使得分号插入变得无用。
很显然,我不想让我的解析器丢弃有效的语句并转到下一个语句。
我如何才能做到这一点?这是正确的方法还是我缺少什么?
答案 0 :(得分:1)
我认为这种方法不可行。
请注意,在减少任何错误之前,您必须检测到错误。因此,对于在语句末尾插入分号,您需要将错误产生添加到stmt
,而不是stmt_list
。因此,您最终会遇到这样的情况:
stmt_list
: %empty
| stmt_list stmt
stmt: value ';' { handle_value_stmt(); }
| value error { handle_value_stmt(); }
| [other types of statements...]
那不会插入分号;它只是假装插入了分号。 (如果无法插入分号,则会触发另一个错误。)
但是由于它不涉及词法分析器,所以不管缺少的分号是否位于行尾,都会发生这种情况,这太热情了。因此,理想的解决方案是以某种方式告诉词法分析器生成一个分号标记作为下一个标记。但是在检测到错误的时候,词法分析器已经产生了超前标记,解析器知道了超前标记是什么。它将使用其记录的超前标记继续解析。
此时还存在一个问题,即如何与词法分析器进行通信,因为Mid-Rule Actions不能真正与错误恢复算法配合使用。从理论上讲,您可以使用以下事实:将调用yyerror
报告错误,但这意味着yyerror
必须能够推断出这不是“实际”错误,这意味着将不得不探究yyparse
的胆量。 (我确定这是可能的,但我不知道该怎么做,这在我看来并不值得推荐。)
现在,从理论上讲,可以告诉解析器放弃先行标记,并告诉词法分析器生成一个分号,然后重复刚才发送的令牌。因此,如果您足够顽固,则几乎不可能通过将hack堆积到hack上来完成这项工作。但是最终您将很难维护,验证和测试。 (要确保它在所有极端情况下都有效,这也是一个挑战。)
这并没有考虑其他可以插入分号的情况。
我对ASI的方法是通过弄清楚哪些对连续标记是可能的来简单地分析语法。 (这很容易做到;您只需要构造FIRST和LAST集,然后通读所有产生式,查看连续的符号。)然后,如果输入包含令牌A,后接一个或多个换行符,然后是令牌B,则它是语法中A后跟B是不可能的,那么这是分号插入的候选者。分号插入可能会失败,但是会产生语法错误,因此您不会得到误报。 (您可能必须修复语法错误消息,但是到那时,您至少知道已经插入了分号。)
证明该算法有效是棘手的,因为从理论上讲,在某些情况下可以在A
后跟B
,但是在当前上下文中是不可能的,而{{ 1}}在当前上下文中是可能的。在这种情况下,您可能会错过可能的分号插入。我没有详细介绍最新的JS版本,但是很久以前,当我编写JS词法分析器时,我设法使自己满意,证明没有这种情况。
注意:由于这个问题是在评论中提出的,所以我会挥手致意,尽管我确实不建议您采用这种方法。
如果不深入研究野牛的胆量,实际上就不可能“取消转移”一个令牌,包括A ; B
令牌(或多或少是一个真正的令牌)。到error
令牌被移位时,解析已有效地提交给错误产生。因此,如果您想消除该错误,则必须接受该事实并加以解决。
在error
令牌被移位之后,解析器将跳过令牌,直到遇到可移位的令牌。因此,如果您设法在令牌流中插入了自动分号,则可以将该令牌用作防护:
error
但是,您可能没有设法插入自动分号,在这种情况下,您确实需要报告语法错误(并可能尝试重新同步)。上面的规则只是将令牌默默地丢弃到下一个分号,这肯定是错误的。因此,第一种近似方法是让您的ASI插入程序始终插入某些东西,可以将其用作错误产生的保护措施
stmt: value ';' { handle_value_stmt(); }
| value error ';' { handle_value_stmt(); }
这对于“中止错误”处理就足够了,但是如果您想进行错误恢复,则需要做更多的黑客操作。
正如我所说,我真的不建议您沿这条路线走。即使最终结果可行,最终结果也不会很漂亮(在您未考虑的情况下,您仍然可能会发现您认为有效的代码在实际用户输入上失败了。)