您可能可以使解析器生成器得到正确的结果,但这可能需要修改解析器生成器的骨架。
我知道三种可行的算法;没有一个是完美的。
-
如果出现以下情况,请在行尾插入显式语句终止符:
A。前一个标记不是语句终止符,并且
b.可以移动语句终止符。
-
在以下情况下,在不可移动标记(Ecmascript 中的“违规标记”)之前插入显式语句终止符:
A。有问题的标记位于行的开头,或者是}
或者是输入结束标记,并且
b.移动语句终止符不会导致空语句生成量的减少。 [1]
列出所有代币对的清单。对于每个标记对,确定是否适合用语句终止符替换行结束符。您也许可以使用上述算法之一来计算此表。
算法3是最容易实现的,但也是最难计算的。而且每次修改语法时可能都需要调整表格,这会大大增加修改语法的难度。如果您可以计算标记对表,则词法分析器可以处理插入语句终止符。 (如果您的语法是运算符优先语法,那么您可以在任何没有优先关系的标记之间插入语句终止符。但是,即使这样,您也可能希望针对受限上下文进行一些调整。)
如果您可以在不破坏上下文的情况下向解析器查询标记的可移动性,则可以在解析器中实现算法 1 和 2。最近版本的 bison 允许您指定他们所谓的“LAC”(LookAhead Correction),这涉及到这样做。从概念上讲,解析器堆栈被复制并且解析器尝试处理令牌;如果令牌最终被转移(可能在一定次数的减少之后),而没有触发错误产生,则令牌是有效前瞻的一部分。我还没有查看实现,但很明显实际上没有必要复制堆栈来计算可转移性。无论如何,如果你想使用它,你必须对该设施进行逆向工程到 Lemon 中,这将是一个有趣的练习,可能不会太困难。 (您还需要修改 bison 骨架才能执行此操作,但从 LAC 实现开始可能会更容易。bison 目前仅使用 LAC 来生成更好的错误消息,但它确实涉及测试每个令牌的可转移性。)
在上述所有算法中,需要注意的一件事是可能以括号表达式开头的语句。 Ecmascript 尤其会犯这个错误(恕我直言)。 Ecmascript 示例,直接来自报告:
a = b + c
(d + e).print()
Ecmascript 会将其解析为单个语句,因为c(d + e)
是语法上有效的函数调用。最后,(
不是一个有问题的令牌,因为它可以被转移。不过,程序员不太可能有意这样做,并且在执行代码(如果执行的话)之前不会产生错误。
请注意,算法 1 会在第一行末尾插入一个语句终止符,但同样不会标记歧义。这更有可能是程序员的意图,但未标记的歧义仍然令人烦恼。
Lua 5.1 会将上面的示例视为错误,因为它不允许在函数对象和(
在调用表达式中。然而,Lua 5.2 的行为类似于 Ecmascript。
另一个经典的歧义是return
(以及可能的其他陈述)其中有一个optional表达。在 ECMAScript 中,return <expr>
属于限制生产;关键字和表达式之间不允许有换行符,因此return
在行尾自动插入分号。在 Lua 中,它并不含糊,因为return
语句后面不能跟另一个语句。
Notes:
- Ecmascript 还要求将语句终止符标记解析为语句终止符,尽管它并没有完全这么说;它不允许在 a 的迭代器子句中使用分号
for
自动插入语句。其算法还包括在两个上下文中强制插入分号:在return/throw/continue/break
出现在行尾和之前的标记++/--
出现在行开头的标记。