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