词法分析器和解析器的职责

Mar*_*ulz 5 compiler-construction parsing lexical-analysis tokenize

我目前正在为一种简单的编程语言实现一个词法分析器。到目前为止,我可以正确地标记标识符、赋值符号和整数文字;一般来说,空白是微不足道的。

对于输入foo = 42,可识别三个标记:

  1. foo(标识符)
  2. =(象征)
  3. 42(整数文字)

到目前为止,一切都很好。但是,考虑输入,由于和之间缺少(大量)空格foo = 42bar,该输入无效。我的词法分析器错误地识别以下标记:42bar

  1. foo(标识符)
  2. =(象征)
  3. 42(整数文字)
  4. bar(标识符)

一旦词法分析器看到数字4,它就会继续读取,直到遇到非数字。因此,它使用2并将其存储42为整数文字标记。由于空格无关紧要,因此词法分析器会丢弃所有空格(如果有的话)并开始读取下一个标记:它会找到标识符bar

现在,我的问题是:词法分析器仍然有责任认识到该位置不允许使用标识符吗?或者该检查属于解析器的职责吗?

use*_*421 5

我不同意这里的其他答案。它应该由词法分析器完成。如果数字后面的字符不是空格或特殊字符,则说明您处于非法标记中间,特别是不以字母开头的标识符。

或者只是分别返回 45 和 'bar' 并让解析器将其作为语法错误处理。


ric*_*ici 5

42foo我认为对于是否应该被识别为无效数字或两个令牌的问题没有达成共识。这是一个风格问题,这两种用法在众所周知的语言中都很常见。

\n\n

例如:

\n\n
$ python -c \'print 42and False\'\nFalse\n\n$ lua -e \'print(42and false)\'\nlua: (command line):1: malformed number near \'42a\'\n\n$ perl -le \'print 42and 0\'\n42\n\n# Not an idiosyncracy of tcc; it\'s defined by the standard\n$ tcc -D"and=&&" -run - <<<"main(){return 42and 0;}"\nstdin:1: error: invalid number\n\n# gcc has better error messages\n$ gcc -D"and=&&" -x c - <<<"main(){return 42and 0;}" && ./a.out\n<stdin>: In function \xe2\x80\x98main\xe2\x80\x99:\n<stdin>:1:15: error: invalid suffix "and" on integer constant\n<stdin>:1:21: error: expected \xe2\x80\x98;\xe2\x80\x99 before numeric constant\n\n$ ruby -le \'print 42and 1\'\n42\n\n# And now for something completely different (explained below)\n$ awk \'BEGIN{print 42foo + 3}\'\n423\n
Run Code Online (Sandbox Code Playgroud)\n\n

因此,这两种可能性都是通用的。

\n\n

如果您因为认为数字和单词应该用空格分隔而要拒绝它,则应该在词法分析器中拒绝它。解析器无法(或不应该)知道空格是否分隔两个标记。与 的有效性无关42and,片段42 + 1, 42+1, 和42+ 1) 都应该被解析。(也许,在《堡垒》中除外。但这是一个异常情况。)如果您不介意将数字和单词放在一起,那么当(且仅当)它是语法错误时让解析器拒绝它。

\n\n

附带说明一下,在 C 和 C++ 中,42and最初被词法为“预处理器编号”。预处理后,需要对其进行重新分析,此时会生成错误消息。这种奇怪行为的原因是,将两个片段粘贴在一起以生成有效数字是完全合法的:

\n\n
$ gcc -D"c_(x,y)=x##y" -D"c(x,y)=c_(x,y)"  -x c - <<<"int main(){return c(12E,1F);}"\n$ ./a.out; echo $?\n120\n
Run Code Online (Sandbox Code Playgroud)\n\n

12E都是1F无效整数,但与##运算符粘贴在一起,它们形成了一个完全合法的浮点数。该##运算符仅适用于单个标记,因此12E1F都需要作为单个标记进行词法分析。c(12E+,1F)不会工作,但c(12E0,1F)也很好。

\n\n

这也是为什么在 C 语言中应该始终在+运算符周围放置空格:经典技巧 C 问题:“什么是值0x1E+2?”

\n\n

最后,对 awk 行的解释:

\n\n
$ awk \'BEGIN{print 42foo + 3}\'\n423\n
Run Code Online (Sandbox Code Playgroud)\n\n

它由 awk 进行词法分析,BEGIN{print 42 foo + 3}然后对其进行解析,就好像它已被写入一样BEGIN{print (42)(foo + 3);}。在 awk 中,字符串连接是在没有运算符的情况下编写的,但它的结合不如任何算术运算符紧密。因此,通常的建议是在涉及连接的表达式中使用显式括号,除非它们非常简单。0(此外,如果以算术方式使用并且""用作字符串,则假定未定义的变量具有该值。)

\n