通常如何解析注释?


31

一般如何用编程语言和标记来处理注释?我正在为某些自定义标记语言编写解析器,并希望遵循最少惊喜原则,因此我试图确定一般约定。

例如,注释是否应嵌入令牌中,从而“干扰”令牌?通常,是这样的:

Sys/* comment */tem.out.println()

有效?

另外,如果语言对新行敏感,并且注释跨越新行,是否应考虑新行?

stuff stuff /* this is comment
this is still comment */more stuff 

被视为

stuff stuff more stuff

要么

stuff stuff
more stuff

我知道几种特定的语言在做什么,我也没有在寻求意见,而是在寻找是否:是否普遍达成共识,标记对代币和新行的一般期望是什么?


我的特定上下文是类似Wiki的标记。


注释中是否包含换行符?为什么将其与注释中的其他字符区别对待?

1
@Snowman有这种观点,但另一方面,如果标记“ x”具有特殊含义,则它是该行上的第一个标记,并且对于查看源代码的人和该源而言,它似乎都是该行上的第一个标记。解析器逐行读取。似乎是一个难题,所以我问了一个问题。
雪橇

4
不久前,我需要完全按照规范进行操作,发现gcc的文档是一个很好的资源。您可能没有考虑过一些奇怪的极端情况。
Karl Bielefeldt 2015年

Answers:


40

通常,注释是在标记化过程中但在解析之前被扫描(并丢弃)的。即使注释周围没有空格,注释也像令牌分隔符一样工作。

如您所指出的,C规范明确指出注释被单个空格替换。不过,这只是规范术语,因为现实世界中的解析器实际上不会替换任何内容,而只会像扫描并丢弃空白字符一样扫描并丢弃注释。但是它以一种简单的方式说明了注释与空格的分隔方式是分隔注释。

注释的内容将被忽略,因此多行注释中的换行符无效。对换行敏感的语言(Python和Visual Basic)通常没有多行注释,但是JavaScript是一种例外。例如:

return /*
       */ 17

相当于

return 17

return
17

单行注释保留换行符,即

return // single line comment
    17

相当于

return
17

return 17

由于注释已扫描但未解析,因此它们往往不会嵌套。所以

 /*  /* nested comment */ */

是语法错误,因为注释由第一个打开,由第一个/*关闭*/


3
在大多数语言中,行内注释(/* like this */)等于单个空格,而以EOL结尾的注释(// like this)被视为空格。
9000

@JacquesB,所以我正在考虑将注释从源头全​​部替换为零宽度的空格,这似乎与您的建议等同。
雪橇

1
@artb普通空间应该可以正常工作,并且位于ASCII代码页中。
约翰·德沃夏克

@JanDvorak空格将影响外观并消除理解,并且更接近“注释实际上不存在”的语义。主要的渲染输出将是HTML,因此在我的情况下,ASCII不是因为浏览器支持Unicode而引起的问题。就是说,我相信C标准要求用单个空格代替注释。
雪橇

1
某些语言(尤其是Racket)确实嵌套了多行注释:(define x #| this is #| a sub-comment |# the main comment |# 3) xyields 3
wchargin

9

要回答这个问题:

大家普遍认为加价会带来什么期望?

我要说的是,没有人期望嵌入令牌中的评论是合法的。

根据经验,注释应与空格相同。任何具有多余空格的有效位置也应被允许嵌入注释。唯一的例外是字符串:

trace("Hello /*world*/") // should print Hello /*world*/

在字符串中支持注释会很奇怪,并且使它们变得乏味!


2
从来没有考虑过字符串,这是一个很好的例子。我目前的想法是在注释的开始和结束之间做一个简单的正则表达式,并用一个空格替换它。那将使您的案子绊倒。
雪橇

3
+1表示转义字符串。尽管在您的示例中,我通常希望它能够打印Hello /* world*/!而不是取消注释定界符。另外,欢迎来到程序员!
8bittree

1
谢谢8bittree!这完全是我的意思。有趣的是,我还需要躲避**在我的答案....
康纳·克拉克

2
通常,@ ArtB会通过边际案例以及与其他功能的交互来“棘手的解析”,因此一开始最好避免使用。
hobbs 2015年

7

在对空格不敏感的语言中,忽略的字符(即空格或作为注释一部分的空格)定界标记。

因此,例如Sys tem有两个令牌,而System有一个。如果您进行比较new Foo(),则此功能的用途可能更加明显,newFoo()其中一个将构造Foowhile另一个调用的实例newFoo

注释可以起到与空白相同的作用,例如new/**/Foo(),与相同new Foo()。当然,这可能会更复杂,例如,new /**/ /**/ Foo()或者什么都不会。

从技术上讲,应该可以在标识符中包含注释,但是我怀疑它是否特别实用。

现在,什么是空格敏感的语言?

Python浮现在脑海,它有一个非常简单的答案:没有块注释。您可以从中开始注释,#然后解析器的工作原理就好像该行的其余部分不存在,而只是一个换行符。

与此相反,jade允许块注释,当您返回相同的缩进级别时,块结束。例:

body
  //-
    As much text as you want
    can go here.
  p this is no longer part of the comment

因此,在这个领域中,我不会说您可以说通常如何处理事情。似乎共有的地方是,注释总是以行尾结尾,这意味着所有注释的行为与换行完全相同。


嗯,换行是真正的问题,因为我们使用HTML \ XML语法进行注释,因此它将是多行。
雪橇

3
@ArtB如果您使用的是HTML / XML语法,那么简单地使用它们的行为可能是明智的。
8bittree

1
@ 8bittree有道理,应该考虑一下。我将保留该问题,因为这样将更加有用。
雪橇2015年

3

过去,作为词法分析的一部分,我将注释变成单个标记。字符串也是如此。从那里开始,生活很轻松。

在我构建的最后一个解析器的特定情况下,将转义规则传递到顶级解析例程。转义规则用于处理标记,例如与核心语法内联的注释标记。通常,这些令牌被丢弃。

这样做的结果是,您在标识符中间添加了注释的示例中,标识符将不是单个标识符-这是我使用过的所有语言(从内存中)的预期行为。

字符串中的注释大小写应由词法分析隐式处理。处理字符串的规则对注释不感兴趣,因此注释被视为字符串的内容。注释中的字符串(或带引号的文字)也是如此-字符串是注释的一部分,显式地是单个标记;处理注释的规则对字符串不感兴趣。

我希望这有道理/有帮助。


因此,如果您有诸如的代码console.log(/*a comment containing "quotes" is possible*/ "and a string containing /*slash-star, star-slash*/ is possible"),则注释中的引号和字符串中的注释语法,那么词法分析器如何知道正确地将其标记化?您能否编辑您的答案,并提供这些情况的一般说明?
chharvey

1

这取决于解析器的用途。如果您编写一个解析器来构建要编译的解析树,则注释除了可能分隔标记(例如,方法/ 注释 /(// 注释 /))之外没有语义值。在这种情况下,将其视为空格。

如果您的解析器是将一种源语言翻译成另一种源语言的转译器的一部分,或者如果您的解析器是使用源语言的编译单元进行解析的预处理器,则对其进行解析,修改并将修改后的版本以相同的源语言写回,像其他任何事情一样变得非常重要。

同样,如果注释中包含元信息,并且您特别喜欢注释,例如像生成JavaDoc一样生成API文档时,注释突然变得非常重要。

在这里,注释通常附加在令牌本身上。如果找到评论,则将其附加为令牌的评论。由于一个令牌可以在之前和之后具有多个令牌,因此再次取决于目的如何处理这些注释。

用注释注释非注释标记的想法是将语法中的注释全部删除。

一旦有了解析树,一些AST就会开始解压缩由每个令牌自己的AST-Element表示每个令牌的注释,但这些注释会附加到通常包含关系之外的另一个AST-Element上。一个好主意是检查所有解析器/ AST实现中的开源IDE中可用的源语言。

一种非常好的实现是用于Java语言的Eclipse编译器基础结构。据我所知,它们在标记化过程中保留注释,并在AST中表示注释。同样,此解析器/ AST实现保留格式。

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.