在语法中表示语句终止换行符?

Ric*_*art 4 grammar parsing yacc lemon

许多编程语言都有以行结尾终止的语句。但是,通常情况下,如果解析器无法理解该行,则在语句中间允许行结尾;例如,

a = 3 +
4
Run Code Online (Sandbox Code Playgroud)

...将在 Ruby 和 Python* 中解析为 statement a = 3+4,因为a = 3+没有任何意义。换句话说,换行符会被忽略,因为它会导致解析错误。

我的问题是:如何使用标记器和解析器简单/优雅地完成相同的行为?我使用 Lemon 作为解析器生成器,如果它有任何不同的话(尽管我也将这个问题标记为 yacc,因为我确定该解决方案同样适用于两个程序)。

这是我现在的做法:在没有句法歧义的任何情况下,允许可选地出现语句终止符。换句话说,像

expression ::= identifier PLUS identifier statement_terminator.
expression ::= identifier PLUS statement_terminator identifier statement_terminator.
Run Code Online (Sandbox Code Playgroud)

...换句话说,可以在加号后使用换行符,因为这不会对语法的歧义产生任何影响。我担心这会增加语法的大小,我有很多机会遗漏案例或在语法中引入细微的错误。有没有更简单的方法来做到这一点?

编辑*:实际上,该代码示例不适用于 Python。但是,如果您传入这样的内容,Python 实际上会忽略换行符:

print (1, 2,
3)
Run Code Online (Sandbox Code Playgroud)

ric*_*ici 5

您可能可以让解析器生成器做到这一点,但它可能需要修改解析器生成器的骨架。

我知道三种可能的算法;没有一个是完美的。

  1. 如果出现以下情况,请在行尾插入显式语句终止符:

    一种。之前的标记不是语句终止符,并且

    湾 可以移动语句终止符。

  2. 在不可移动的标记(Ecmascript 中的“违规标记”)之前插入显式语句终止符,如果:

    一种。有问题的标记位于一行的开头,或者是}或 是输入标记的结尾,并且

    湾 移动语句终止符不会导致空语句产生的减少。[1]

  3. 清点所有代币对。对于每个标记对,决定用语句终止符替换行尾是否合适。您或许可以使用上述算法之一来计算此表。

算法 3 最容易实现,但最难实现。而且每次修改语法都可能需要调整表格,这会大大增加修改语法的难度。如果您可以计算令牌对表,则词法分析器可以处理插入语句终止符。(如果您的文法是运算符优先文法,那么您可以在任何没有优先关系的标记之间插入语句终止符。但是,即便如此,您也可能希望对受限上下文进行一些调整。)

如果您可以在不破坏上下文的情况下向解析器查询标记的可移动性,则算法 1 和 2 可以在解析器中实现。最新版本的 bison 允许您指定他们所谓的“LAC”(LookAhead Correction),这涉及到这样做。从概念上讲,解析器堆栈被复制,解析器尝试处理令牌;如果令牌最终被移动,可能在一些减少之后,没有触发错误产生,那么令牌是有效前瞻的一部分。我还没有看过实现,但很明显,实际上没有必要复制堆栈来计算可移动性。无论如何,如果您想使用它,您必须将该设施逆向工程为 Lemon,这将是一个有趣的练习,可能不会太难。(你' d 还需要修改野牛骨架来做到这一点,但从 LAC 实施开始可能会更容易。LAC 目前仅被野牛用来生成更好的错误消息,但它确实涉及测试每个令牌的可移动性。)

在上述所有算法中,需要注意的一件事是可能以括号表达式开头的语句。特别是,Ecmascript 弄错了(恕我直言)。Ecmascript 示例,直接来自报告:

a = b + c
(d + e).print()
Run Code Online (Sandbox Code Playgroud)

Ecmascript 会将其解析为单个语句,因为它c(d + e)是一个语法上有效的函数调用。因此,(不是一个有问题的令牌,因为它可以被转移。但是,程序员不太可能打算这样做,并且如果执行代码,则在执行代码之前不会产生错误。

请注意,算法 1 会在第一行的末尾插入一个语句终止符,但同样不会标记歧义。这更有可能是程序员的意图,但未标记的歧义仍然令人讨厌。

Lua 5.1 会将上面的示例视为错误,因为它不允许在函数对象和(in 调用表达式之间出现新行。但是,Lua 5.2 的行为类似于 Ecmascript。

另一个经典的歧义是return(可能还有其他语句)具有可选表达式。在 Ecmascript 中,return <expr>是受限生成;关键字和表达式之间不允许有换行符,因此return在行尾自动插入分号。在 Lua 中,它没有歧义,因为一个return语句后面不能跟另一个语句。


笔记:

  1. Ecmascript 还要求语句终止符被解析为语句终止符,尽管它并没有这么说;它不允许for自动插入语句的迭代器子句中的分号。它的算法还包括在两个上下文中强制分号插入:return/throw/continue/break出现在行尾的标记之后,以及++/--出现在行首的标记之前。