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

时间:2013-07-15 02:42:30

标签: parsing grammar yacc lemon

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

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.

......换句话说,可以在加号后使用换行符,因为这不会对语法的歧义产生任何影响。我担心这会使语法大小膨胀,我有很多机会错过案例或在语法中引入微妙的错误。有更简单的方法吗?

编辑*:实际上,该代码示例不适用于Python。事实上,如果传入类似的内容,Python确实会忽略换行符:

print (1, 2,
3)

1 个答案:

答案 0 :(得分:4)

你可能会使解析器生成器正确,但它可能需要修改解析器生成器的骨架。

我知道有三种似是而非的算法;没有一个是完美的。

  1. 如果符合以下条件,则在行尾插入一个显式语句终结符:

    一个。前一个标记不是语句终止符,

    湾可以转移语句终止符。

  2. 在不可操作的令牌之前插入显式语句终止符(在“Ecmascript”中说出“违规令牌”),如果:

    一个。违规令牌位于一行的开头,或者是}或是输入结束令牌,

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

  3. 制作所有令牌对的清单。对于每个令牌对,决定是否适合用语句终止符替换行尾。您可以使用上述算法之一计算此表。

  4. 算法3是最容易实现的,但最难解决。并且您可能需要在每次修改语法时调整表格,这将大大增加修改语法的难度。如果您可以计算令牌对表,那么词法分析器可以处理插入语句终止符。 (如果你的语法是一个运算符优先语法,那么你可以在没有优先关系的任何一对标记之间插入一个语句终止符。但是,即使这样你也可能希望对受限制的语境进行一些调整。)

    如果您可以在不破坏上下文的情况下向解析器查询令牌的可移植性,则可以在解析器中实现算法1和算法2。最新版本的野牛允许您指定他们称之为“LAC”(LookAhead Correction)的内容,其中包括这样做。从概念上讲,解析器堆栈被复制,解析器尝试处理令牌;如果令牌最终被移位,可能在经过一定数量的减少后,而不会触发错误产生,那么令牌就是有效前瞻的一部分。我没有看过实现,但很明显,实际上并不需要复制堆栈来计算可移植性。无论如何,如果你想使用它,你必须将设施逆向设计成柠檬,这将是一项有趣的练习,可能并不太难。 (您还需要修改bison框架来执行此操作,但从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语句不能跟随另一个语句。


    注意:

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