Lexer:把源码切成 token
本节阅读量:上一节先看了目标:我们希望最后得到 AST。
但源码一开始不是 AST,它只是文本:
|
|
如果直接把这段文本交给 parser,parser 要一边处理字符,一边理解结构,会很乱。
所以第一步先做一件更小的事:
|
|
这一步由 lexer 完成。
token 是什么
token 可以先理解成:
|
|
源码:
|
|
切成 token 后是:
|
|
第一章只有这几类 token:
|
|
第一章先不单独定义 Token 类型,直接用 std::string 保存每个 token。也就是说,lexer 返回的是一串字符串小块。
注意,lexer 还不理解整棵程序。
它不会判断 (+ 40 2) 是不是一个合法加法,也不会判断 + 后面是不是正好有两个表达式。它只负责把字符切成字符串小块。
|
|
先让括号独立出来
代码在:
|
|
如果只按空白切分,(+ 40 2) 会遇到一个麻烦:
|
|
但在 Lisp 风格语法里,括号本身就是重要 token。我们希望它们独立出来:
|
|
所以代码先给括号两边补空格:
|
|
这段代码可以读成:
|
|
所以:
|
|
会先变成类似:
|
|
现在括号已经不会和旁边的字符粘在一起了。
stringstream 的抽取逻辑
接下来用 std::istringstream 按空白抽出一个个小块:
|
|
std::istringstream 可以先理解成:
|
|
它有点像 std::cin,只是 std::cin 从键盘读,std::istringstream 从字符串读。
这里最核心的是这一句:
|
|
当右边是 std::string 时,它会做三件事:
|
|
比如输入流里是:
|
|
抽取过程可以这样看:
|
|
while (words >> word) 的意思不是“永远循环”。它的意思是:
|
|
每次成功抽出一个 word,就把它放进 tokens:
|
|
最后得到:
|
|
这里的空格、换行、tab 都只是分隔符,不会进入 token。
完整 lexer
第一章的 lex 函数很短:
|
|
这段代码做的事情可以压成三步:
|
|
所以:
|
|
会被切成:
|
|
也就是:
|
|
lexer 不负责判断结构
第一章的 lexer 很朴素。它只切字符串,不做深判断。
比如:
|
|
lexer 只会得到:
|
|
它不会在这里决定 -7 是不是合法整数。这个判断交给 parser 里的 is_integer。
再看这个:
|
|
给括号补空格后,会变成类似:
|
|
所以 token 是:
|
|
lexer 不会报错。它只是照实切出来。
后面的 parser 看到左括号后,会期待下一个 token 是 "+"。但它实际看到的是 "+40",于是才报错。
这也是第一章的一个约定:
|
|
所以:
|
|
下一节的 parser 会接过这串 token,把它们组装成真正的表达式结构。