词法分析器/语法分析器的歧义性

5

词法分析器如何解决这种歧义?

/*/*/

我该怎么做才能让它不仅仅是说“哦,是多行注释的开始,接着是另一个多行注释”呢?
贪心词法分析器会返回以下标记吗?
/* /* */
我正在编写一个CSS移位归约解析器,但这个简单的注释问题却在我的路上。如果您想要更多背景信息,可以阅读this question
更新
抱歉,我第一次没有提及这一点。我计划以这种形式添加到CSS语言的扩展/* @ func ( args, ... ) */,但我不想混淆了解CSS但不了解我的扩展注释的编辑器。这就是为什么词法分析器不能忽略注释的原因。

2
正如您在“此问题”的回答中所指出的那样,词法分析器应该进入“注释状态”,并且丢弃输入,直到它看到一个词元将其移出该状态。解析器不应该看到注释,而词法分析器除了确定注释何时结束之外,也不应该看到注释的内容。 - msw
@msw:当然,让解析器永远不看到注释并不是一个硬性规定。你可以将注释视为标记并将其提供给解析器来做一些很酷的事情——只需看看Python文档字符串即可。 - Brian McKenna
实际上,我特别指的是C风格的注释及其与语法的词汇关系。我本可以更清楚地指出OPs评论,他不应混淆词汇和句法解释。同意Python文档字符串很有用(以及javadoc等)。我还没有看过Python语法,但我敢打赌<stand-alone-string>有一个产生式。 - msw
事实是,我真的想将注释传递给解析器。 - John Leidegren
6个回答

10

一种方法是让词法分析器在遇到第一个/*时进入不同的内部状态。例如,flex 称之为“起始条件”(匹配C风格注释是该页面上的示例之一)。


我相信这对我来说是正确的选择。因为我忽略了我真正想解析评论的部分... - John Leidegren
@John Leidegren:确保你理解包含和不包含的条件。它们确实非常方便。 - leppie
@leppie - 在lex中吗?我现在反对使用工具。我可能会考虑稍后使用词法分析器生成器,但我真的想深入了解编写词法分析器和解析器的基础知识。 - John Leidegren
@John Leidegren: 我知道它们在Flex中。但是如果你自己编写,通过堆栈和集合实现起来非常简单。 - leppie

6
最简单的方法可能是将注释作为一个单一的标记进行词法分析 - 也就是说,不发出“开始注释”标记,而是继续读取输入,直到可以发出包括整个/*(anything)*/部分的“注释块”标记。
由于注释与可执行代码的实际解析无关,因此让词法分析器将其基本上剥离(或者至少将其合并为一个标记)是可以的。您不关心注释内的标记匹配。

问题是我要以 /* @ func ( args, ... ) */ 形式添加扩展,所以我不能抛弃注释。此外,CSS2 规范说明 / * 是不同的分隔符标记。 - John Leidegren
1
如果您选择这样做,可以递归解析注释标记一旦它们被识别出来 - 根据您的扩展程序的复杂程度,它们可能能够从一个平面注释字符串中进行正则表达式处理,而无需进行词法分析。 - Amber
使用正则表达式匹配注释听起来很实用,但感觉不太正式。我想现在先避免这个。现在我想专注于理解词法分析的基础知识。 - John Leidegren

3
在大多数编程语言中,这是不含糊的:第一个斜杠和星号被消耗掉以生成“开始多行注释”标记。其后是一个斜杠,在注释中是普通的“内容”,最后两个字符是“结束多行注释”标记。
由于前两个字符已经被消耗掉,第一个星号不能用来生成注释结束标记。我刚才提到它可以产生第二个“开始注释”的标记……哎呀,这可能会有问题,这取决于解析器可用的上下文量。
我在这里谈论标记,假设解析器处理注释。但同样适用于词法分析器,其中底层规则是以'/*'开头,然后不停止直到找到'*/'。实际上,整个注释的词法分析器级别处理不会被第二个“开始注释”所混淆。

我认为OP所关心的歧义不是第一个/*/,而是第二个/*/ - 他们担心他们的词法分析器会发出两个“开始注释”标记,然后消耗了5个字符中的4个,只剩下一个/字符(因此没有“结束注释”标记)。我认为我的答案更明确地说明了如何在词法分析器级别上避免这种情况。 - Amber
@Dav:没错,你说得对;我在编辑时也发现了第二个“开始注释”标记的问题,并且也得出结论,处理整个注释的最简单方法是在词法分析器层面上进行。然而,有些应用程序需要解析注释中的内容(例如:文档生成应用程序等),对于它们来说,解析器级别需要以某种形式获取一些上下文信息才能摆脱这个困境。 - mjv

1

CSS不支持嵌套注释,因此您的示例通常会解析为单个标记COMMENT。也就是说,词法分析器将把/*视为开始注释标记,然后消耗一切直到包括*/序列。


1
你可以使用反引号(\``)来标记代码中的文本段,而不是整行,例如:/*`。 - Amber

0
使用正则表达式算法,从字符串开头开始搜索,一直工作到当前位置。
if (chars[currentLocation] == '/' and chars[currentLocation - 1] == '*') {
  for (int i = currentLocation - 2; i >= 0; i --) {
    if (chars[i] == '/' && chars[i + 1] == '*') {
      // .......
    }
  }
}

这就像是贪婪地从底部应用正则表达式/\*([^\*]|\*[^\/])\*/


0

解决这个问题的一种方法是让你的词法分析器返回:

/
*
/
*
/

然后让你的解析器从那里处理它。对于大多数编程语言,这可能是我要做的,因为/'s和*'s也可以用于乘法等其他复杂的操作,这些操作对词法分析器来说过于复杂。词法分析器应该只返回基本符号

如果令牌的内容开始过多地依赖上下文,则您要寻找的可能是更简单的令牌。

话虽如此,CSS不是一种编程语言,因此/'s和*'s不能被重载。实际上,他们不能用于任何其他东西,而只能用作注释。除非你有充分的理由,否则我很想把整个东西都传递为注释令牌:/\*.*\*/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接