无扫描器解析与“悬空其他问题”有什么关系?


13

我不理解维基百科上有关悬空其他问题的文章

[悬而未决的问题]是编译器构造中经常出现的问题,尤其是无扫描器解析。

有人可以向我解释无扫描仪解析技术如何加剧该问题?在我看来,问题出在语法上(因为它模棱两可),而不是语法分析技术的选择。我想念什么?


2
我唯一能想到的是,无扫描器的解析器需要更复杂的语法,这使得提供启发式方法来解决歧义变得更加困难。
Giorgio

3
@Robert Harvey:关键是这个假设必须由语法树反映出来。如果语法允许为字符串派生两个不同的语法树if a then if b then s1 else s2,则该语法是不明确的。
Giorgio

1
@RobertHarvey定义语言的一种常用方法是使用无上下文语法,并在必要时加上一堆规则来消除语法歧义。

2
并非所有无扫描器的解析器创建的都是相同的。例如,对于PEG或GLR,悬挂的其他行为始终是可预测的。
SK-logic

1
[悬而未决的问题]与无扫描仪解析无关。[悬而未决的问题]与LR(自底向上)解析器的移位减少操作有关。AFAIK
2014年

Answers:


6

我最好的猜测是Wikipedia文章中的句子是由对E. Visser工作的误解造成的。

无扫描器解析器的语法(即,将语言描述为一组字符序列而不是一组令牌序列,而将令牌分别描述为字符串)的语法往往有很多歧义。E. Visser论文针对无扫描广义LR解析器的消歧过滤器(*)提出了几种解决歧义的机制,其中一种机制对于解决悬空的其他问题很有用。但是该论文并未指出精确的含糊不清(“悬而未决的问题”)与无扫描仪解析器有关(甚至该机制对无扫描仪解析器也特别有用)。

它提议解决该问题的机制并非隐含陈述,因为另一个歧义解决机制(运算符优先级和优先级)似乎也与所考虑的解析器的无扫描程序性质完全无关(例如,考虑那些歧义不能出现在常规语法中,是由嵌套产生的,而由最长匹配规则处理的则可以。


(*)这可能是Wikipedia关于无扫描仪解析器的文章的基础,即使它们引用了E. Visser的Scannerless Generalized-LR Parsing也是如此


13

只是为了说明问题,“悬空其他问题”在代码语法规范中是模棱两可的,在下一个if和else情况下(可能属于if的情况),可能尚不清楚。

最简单和经典的示例:

if(conditionA)
if(conditionB)
   doFoo();
else
   doBar();

目前还不清楚,那些谁也不知道由心脏的语言规范,其中的细节if得到else(和这个特殊的代码片段是在半打语言有效,但可以在每个不同地执行)。

Dangling Else构造对无扫描器解析器实现提出了潜在的问题,因为该策略是一次将文件流吸引到一个字符,直到解析器看到它足以进行标记化为止(深入到要编译的汇编语言或中间语言中) 。这使解析器可以保持最小状态。一旦它认为有足够的信息来将已解析的令牌写入文件,它就会这样做。这就是无扫描器解析器的最终目标。快速,简单,轻便的编译。

假设标点符号之前或之后的换行符和空格是没有意义的(就像在大多数C样式语言中一样),则此语句在编译器中将显示为:

if(conditionA)if(conditionB)doFoo();else doBar;

完全可以解析到计算机,让我们看看。我一次得到一个角色,直到获得:

if(conditionA)

哦,我知道这是什么意思(在C#中),它的意思是“ pushconditionA到eval堆栈上,brfalse如果不正确,则调用以跳至下一个分号后的语句”。现在我看不到分号,所以现在我将在该指令之后将跳转偏移量设置为下一个空格,随着我插入更多指令,我将增加该偏移量,直到看到分号为止。继续解析...

if(conditionB)

好的,这解析为一对类似的IL操作,它在我刚解析的指令之后立即执行。我看不到分号,因此我将前两个语句的跳转偏移量增加两个命令的长度(一个用于推送,一个用于中断),然后继续查找。

doFoo();

好吧,那很容易。那就是“ calldoFoo”。而且是一个分号我看到了什么?好吧,那太好了,到此为止。我将通过这两个命令的长度来增加两个块的跳转偏移,而忘记了我曾经关心过的。好,继续...

else

嗯 这并不像看起来那么简单。好的,我忘记了我刚才在做什么,但是这else意味着在我已经看到的某个地方有一个条件中断语句,所以让我回头看...是的,是的,brfalse在我按下一些“ conditionB”之后堆栈,不管是什么。好的,现在我需要无条件break作为下一条语句。现在将要发出的语句现在绝对是我的有条件休息的目标,因此,我将确保我做对了,然后将增加我放入的无条件休息。继续...

doBar();

这很容易。“ calldoBar”。而且有一个分号,而且我从来没有看到任何括号。因此,break无论它是什么,无条件的都应跳到下一条语句,而我会忘记我曾经关心过的内容。


所以,我们有什么...(请注意:现在是10:00 PM,我不希望将位偏移量转换为十六进制或使用这些命令填充函数的完整IL外壳,所以这只是伪IL使用通常会有字节偏移的行号):

ldarg.1 //conditionA
brfalse <line 6> //jumps to "break"
ldarg.2 //conditionB
brfalse <line 7> //jumps to "call doBar"
call doFoo
break <line 8> //jumps beyond statement in scope
call doBar
<line 8 is here>

好吧,如果规则(与大多数C风格语言一样)是else最接近的规则,则实际上可以正确执行if。缩进以跟随执行嵌套,它会像这样执行,如果conditionA为false,则跳过代码段的其余部分:

if(conditionA)
    if(conditionB)
       doFoo();
    else
       doBar();

...但这是出于意外,因为与外部if语句相关的中断跳到break内部 语句末尾的语句if,这使执行指针超出了整个语句。这是不必要的额外跳转,并且如果此示例更加复杂,则以这种方式进行解析和标记可能会不再起作用。

另外,如果语言规范说悬挂是else属于first if,并且如果conditionA为false则执行doBar,而如果conditionA为true而不是conditionB则什么都不会发生,该怎么办?

if(conditionA)
    if(conditionB)
       doFoo();
else
   doBar();

解析器忘记了if有史以来第一个解析器,因此这种简单的解析器算法将无法生成正确的代码,更不用说高效的代码了。

现在,解析器可能已经足够聪明,可以记住较长的ifs和elses了,但是如果语言规范说else两个ifs匹配第一个之后的一个if,则这会导致两个ifs匹配else的问题:

if(conditionA)
    if(conditionB)
       doFoo();
    else
       doBar();
else
    doBaz();

解析器将看到第一个else,与第一个匹配if,然后看到第二个,并陷入恐慌“我到底在做什么”模式。在这一点上,解析器有很多处于可变状态的代码,它宁愿已经推送到输出文件流。

有所有这些问题和假设条件的解决方案。但是,要么聪明的代码增加了解析器算法的复杂性,要​​么使解析器变得笨拙的语言规范增加了语言源代码的冗长性,例如通过要求终止语句(如end if)或括号表示嵌套如果该if语句具有,则将阻塞else(这两种语言通常都以其他语言风格显示)。

这只是几个if语句的一个简单示例,并查看编译器必须做出的所有决定,以及无论如何它很容易搞砸。这是您的问题中来自Wikipedia的无害声明背后的细节。


1
有趣,但是我不能确定那是维基百科文章的目的。它(通过无扫描程序的条目)引用了Eelco Visser的报告,该报告的内容乍一看与您的解释不符。
AProgrammer

3
感谢您的回应,但并未真正解决OP。我不同意这篇文章中关于无扫描程序解析器的目标是什么以及如何实现的假设。实现无扫描器解析器的方法有很多,而本文似乎只涉及有限的子集。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.