【问题标题】:Tokenising whitespace in a half-whitespace sensitive language?用半空格敏感语言标记空格?
【发布时间】:2016-12-10 10:58:05
【问题描述】:

我已经进行了一些搜索,包括再看一遍我面前的红龙书,但我还没有找到明确的答案。大多数人都在谈论缩进方面的空白敏感性,但我不是这样。

我想为一种简单的语言实现转译器。这种语言有一个“命令”的概念,它是一个保留关键字,后跟一些参数。为了让您了解我在说什么,一系列命令可能看起来像这样:

print "hello, world!";
set running 1;
while running @
    read progname;
    launch progname;
    print "continue? 1 = yes, 0 = no";
    readint running;
@

非正式地,您可以将语法视为类似于

<program>    ::= <statement> <program>
<statement>  ::= while <expression> <sequence>
              |  <command> ;
<sequence>   ::= @ <program> @
              |  <statement>
<command>    ::= print <expression>
              |  set <variable> <expression>
              |  read <variable>
              |  readint <variable>
              |  launch <expression>
<expression> ::= <variable>
              |  <string>
              |  <int>

为简单起见,我们可以这样定义以下内容

<string> is an arbitrary sequence of characters surrounded by quotes
<int> is a sequence of characters '0'..'9'
<variable> is a sequence of characters 'a'..'z'

现在这通常不会有任何问题。事实上,只要这个规范,我就有一个工作实现,其中词法分析器默默地吃掉所有的空白。然而,这里有一个问题:

命令的参数必须用空格分隔!

也就是说,写应该是违法的

while running@print"hello";@

尽管就语法而言,这显然不是模棱两可的。关于如何解决这个问题,我有两个想法。

  1. 每当消耗一些空格时输出一个标记,并在语法中包含空格。我怀疑这会使语法变得更加复杂。

  2. 重写语法,而不是“硬编码”每个命令的参数,我有一个“参数”的生产规则来处理空格。它可能看起来像

    <command>   ::= <cmdtype> <arguments>
    <arguments> ::= <argument> <arguments>
    <argument>  ::= <expression>
    <cmdtype>   ::= print | set | read | readint | launch
    

    然后我们可以确保词法分析器以某种方式 (?) 在遇到 &lt;argument&gt; 标记时处理前导空格。但是,这会将处理内置命令的复杂性(除其他外?)的复杂性转移到解析器中。

这通常是如何解决的? 当一种语言的语法在特定的地方需要空格,但在其他地方几乎都是可选的,那么在词法分析器或解析器中处理它是否有意义?

我希望我能稍微修改一下语言的规范,因为这样会更容易实现,但不幸的是,这是一个向后兼容的问题,不可能。

【问题讨论】:

    标签: parsing


    【解决方案1】:

    向后兼容性通常只适用于正确的程序;接受一个以前会因为语法错误而被拒绝的程序不能改变任何有效程序的行为,因此不会违反向后兼容性。

    这在本例中可能无关紧要,但正如您所说,它会大大简化问题,因此似乎值得一提。

    一种解决方案是将空格传递给解析器,然后将其合并到语法中;通常,您将定义一个终端 WS,并从中定义一个非终端用于可选空格:

    <ows> ::= WS |
    

    如果您小心确保终端和非终端中只有一个在任何上下文中有效,这不会影响可解析性,并且生成的语法虽然有点混乱,但仍然可读。优点是它使空白规则明确。

    另一种选择是在词法分析器中处理问题;这可能很简单,但这取决于语言的确切性质。

    根据您的描述,如果两个标记没有用空格分隔,则目标似乎是产生语法错误,除非其中一个标记是“自定界”;在显示的示例中,我相信唯一这样的标记是分号,因为您似乎表明 @ 必须是空格分隔的。 (可能是您的完整语言有更多的自定界标记,但这并不会显着改变问题。)

    这可以通过词法分析器中的单个开始条件来处理(假设您使用的是允许显式状态的词法分析器生成器);读取空格会使您处于任何令牌都有效的状态(这是初始状态,INITIAL,如果您使用的是 lex-derivative)。在另一种状态下,只有自定界标记是有效的。读取令牌后的状态将是受限状态,除非令牌是自定界的。

    这要求每个词法分析器动作都包含一个状态转换动作,但保持语法不变。效果是将混乱从解析器移到扫描器,代价是模糊空白规则。但它可能不会那么混乱,而且它肯定会简化未来向与空格无关的方言的过渡,如果这是你的计划的话。

    还有一个不同的场景,它是一个类似 posix 的 shell,其中标识符(在 shell 语法中称为“单词”)不限于字母字符,还可能包括任何非自定界字符。在 posix shell 中,print"hello, world" 是一个单词,与两个标记序列 print "hello, world" 不同。 (第一个最终将被取消引用到单个令牌printhello, world。)

    这种情况实际上只能在词法上处理,尽管它不一定复杂。它也可能是您的问题的指南;首先,您可以添加一个词法规则,它接受除空格和自定界字符以外的任何字符串;最大咀嚼规则将确保仅在无法将令牌识别为标识符或字符串(或其他有效令牌)时才采取操作,因此您可以在操作中抛出错误。

    这比基于状态的词法分析器更简单,但灵活性稍差。

    【讨论】:

    • 我担心的是,通过在解析器/语法中包含空格,几乎所有的产品都会看起来像&lt;a&gt; ::= &lt;ows&gt; &lt;b&gt; &lt;ows&gt; &lt;c&gt; &lt;ows&gt;,其中“混乱”有点轻描淡写。但是,您在词法分析器中有两个状态来处理在某些情况下只允许自定界标记的想法非常棒。我想我会走那条路,它比我之前想的要干净得多。当我问一个有点小众的问题并且像你这样的人展示了 Stack Overflow 绝对最好的一面时,我喜欢它。非常感谢!
    • @kqr:如果您的规则是操作数必须用逗号分隔,那么您的语法将在您编写 的位置有一个明确的逗号操作数。我假设您对带有逗号的语法规则没问题;为什么空白版本不同?您会尝试破解词法分析器以抑制“逗号”吗?当您声明“某物”必须存在时,在语法中正确的做法是表达其存在明确性。这就是语法的重点:让读者(和工具)清楚地知道什么是必要的。
    • @IraBaxter 区别可能很微妙:空格几乎在任何地方都是可选的,所以如果我在语法中包含空格,我还必须在语法中几乎每个产生式周围包含“可选空格”。逗号通常在任何地方都不是可选的,因此只能在需要它们的地方将它们包含在语法中。
    • @kqr,语法规则更可能具有&lt;a&gt; ::= &lt;b&gt; &lt;ows&gt; &lt;c&gt; 的形式,因为&lt;a&gt; 的任何使用都将在某些上下文中带有空格注释。您必须避免出现两个连续的空白标记;如果它们是可选的,否则就无法实现,这是模棱两可的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-07
    • 2012-09-03
    • 2023-03-30
    • 2012-07-31
    • 1970-01-01
    相关资源
    最近更新 更多