许多编程语言都有以行结尾的语句。不过,通常情况下,如果解析器无法理解语句的行,则允许在语句中间使用行结尾;例如
a = 3 +
4
将在Ruby和Python*中解析为语句a = 3+4
,因为a = 3+
没有任何意义。换句话说,换行符会被忽略,因为它会导致解析错误。
我的问题是:如何使用标记器和解析器简单/优雅地完成同样的行为?如果有什么不同的话,我会使用Lemon作为解析器生成器(尽管我也将这个问题标记为yacc,因为我确信这个解决方案同样适用于两个程序)。
我现在是这样做的:在任何不存在语法歧义的情况下,都可以选择使用语句终止符。换句话说,类似的东西
expression ::= identifier PLUS identifier statement_terminator.
expression ::= identifier PLUS statement_terminator identifier statement_terminator.
换句话说,在加号后面使用换行符是可以的,因为这不会对语法的歧义产生任何影响。我担心这会扩大语法的规模,我有很多机会错过案例或在语法中引入微妙的错误。有更简单的方法吗?
EDIT*:实际上,这个代码示例不适用于Python。事实上,如果您传入这样的内容,Python确实会忽略换行符:
print (1, 2,
3)
您可能会让解析器生成器实现这一点,但这可能需要修改解析器生成器的骨架。
据我所知,有三种看似合理的算法;没有一个是完美的。
-
如果:,在行的末尾插入一个显式语句终止符
a。前一个令牌不是语句终止符,
b。可以移动语句终止符。
-
如果:
a。违规令牌在一行的开头,或者是
}
,或者是输入令牌的末尾,以及b。移位语句终止符不会导致空语句生成量的减少。[1]
-
清点所有令牌对。对于每一个令牌对,决定用语句终止符替换行结束符是否合适。您可以使用上述算法之一来计算此表。
算法3最容易实现,但最难实现。每次修改语法时,您可能需要调整表格,这将大大增加修改语法的难度。如果可以计算令牌对表,那么插入语句终止符可以由lexer处理。(如果你的语法是运算符优先语法,那么你可以在任何一对没有优先关系的标记之间插入一个语句终止符。但是,即使这样,你也可能希望对受限制的上下文进行一些调整。)
如果可以在不破坏上下文的情况下向解析器查询令牌的可移位性,则可以在解析器中实现算法1和2。最近版本的野牛可以让你指定他们所说的"LAC"(前瞻性校正),这就需要这样做。从概念上讲,解析器堆栈被复制,解析器尝试处理令牌;如果令牌最终被转移,可能是在一些减少之后,而没有触发错误生成,那么令牌就是有效前瞻的一部分。我还没有看过实现,但很明显,实际上并没有必要复制堆栈来计算可移位性。无论如何,如果你想使用它,你必须将该设施反向工程为Lemon,这将是一个有趣的练习,可能不会太难。(您还需要修改bison骨架才能做到这一点,但从LAC实现开始可能会更容易。LAC目前仅由bison用于生成更好的错误消息,但它确实涉及测试每个令牌的可移动性。)
在上述所有算法中,需要注意的一点是,语句可能以带括号的表达式开头。特别是Ecmascript,把这个弄错了(IMHO)。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还要求将语句终止符标记解析为语句终止符,尽管它并没有这么说;它不允许自动插入CCD_ 11语句的迭代器子句中的分号。它的算法还包括在两个上下文中强制插入分号:出现在行尾的
return/throw/continue/break
标记之后和出现在行首的++/--
标记之前