Chapter 2 — step_2 词法分析器:把源码切成 token 流

对应实践Lab02 - step_2 词法分析器 主要修改文件miniCompiler_lab/labs/lab02-step2/framework/student.c

到了这一章,编译器第一次开始真正“看懂一点源码”。但注意,这里的“看懂”还非常有限。词法分析器并不知道一条 if 语句是否合法,也不知道 x + y * z 的优先级是什么。它只负责做一件事:把一串连续字符切成一串更有结构的 token。

这一步之所以重要,是因为后面的语法分析器不适合直接面对原始字符流。对 parser 来说,ifreturn123+{ 这些词法单元已经比单个字符更接近语言结构。

2.1 为什么上一章的能力已经不够了

Chapter 1 里,编译器已经能读文件、写输出,但它对输入内容本身几乎一无所知。无论你给它的是 return 0;,还是一段完全无意义的字符串,它都只会照样吐出固定汇编模板。

这说明第一章建立的是编译器壳子,而不是语言前端

从这一章开始,项目第一次需要回答这样的问题:

  • 这个单词是关键字还是普通标识符?
  • === 为什么不能当成同一个东西?
  • 注释和空白为什么不应该进入后面的语法阶段?

这几个问题看起来零碎,但其实都指向同一件事:先把字符流整理成 token 流。

2.2 先建立一个正确直觉:token 不是“单词”,而是分类后的片段

很多初学者会把 token 粗糙地理解成“源码里的一个词”。这个说法不算完全错,但不够准确。

更好的理解是:

token 是源码中一个被识别、被分类、并且足以交给下一阶段继续处理的片段。

例如在下面这段代码里:

if (sum > 25) {
    return 1;
}

词法分析器看到的不是一句完整控制流语句,而是一段段被分类的片段:

  • if -> 关键字
  • ( -> 左括号
  • sum -> 标识符
  • > -> 比较运算符
  • 25 -> 数字字面量
  • ) -> 右括号
  • { -> 左花括号
  • return -> 关键字
  • 1 -> 数字
  • ; -> 分号
  • } -> 右花括号

一旦这一步完成,后面的 parser 就不用再去猜字符组合,而是可以直接在 token 层面谈结构。

2.3 这一章最该抓住的三类动作

词法分析器看起来分支很多,但先不要被所有 token 类型压住。它最核心的动作可以压成三类:

读取当前位置 -> 判断片段类型 -> 生成一个 token 并推进光标

做 lab 时也按这个顺序理解:

  1. 状态推进:peekpeek_nextadvance
  2. 片段扫描:标识符、数字、字符串
  3. 主分发入口:lexer_next

如果你一开始就陷进所有 case 分支里,很容易被细节淹没。更有效的观察方式是先看“它怎样移动光标,怎样跳过不重要的东西,怎样把重要东西打包成 token”。

2.4 空白和注释为什么要在这一层就处理掉

这一章最容易被低估的函数之一,是跳过空白与注释的逻辑。

这件事之所以应该发生在 lexer,而不是 parser,原因很简单:空格、换行、注释属于书写层面的噪声,不属于语言结构本身。对语法分析来说,return 0;

return      0;   // comment

如果语义完全一样,那就不应该让 parser 去反复应付这些干扰。

也就是说,lexer 的一项重要职责,不只是“认出有用的 token”,还包括“提前丢掉后面阶段不该关心的东西”。

2.5 关键字和标识符为什么要区分

从字符形状上看,ifsum 很像。它们都满足“字母开头,后面跟字母、数字或下划线”。

如果你只靠字符规则扫描,它们都会先落在“像标识符”的这类片段里。真正让 if 变成关键字的,不是扫描方式变了,而是你在扫描完成后,又查了一次关键字表。

这一步非常值得你建立直觉,因为它会反复出现:编译器经常先按一个宽规则识别,再按更具体的语义做二次分类。

2.6 本章 practice 边界

这一章的 practice 路径是:

  • miniCompiler_lab/labs/lab02-step2/

主要修改文件:

  • framework/student.c

验证命令:

cd labs/lab02-step2
make clean && make test

本章 lab 留给你的不是整份 lexer,而是三段最有代表性的骨架:

  1. student_skip_whitespace
  2. student_scan_identifier
  3. student_scan_number

这三个函数刚好覆盖了 lexer 最核心的三类动作:

  • 跳过无用噪声
  • 扫描名字类 token
  • 扫描数字类 token

只要你把这三件事写稳,lexer_next 这类主分发逻辑就会突然变得清晰很多。

2.7 本章最该盯住的验证现象

这一章不要只盯着“测试通过没通过”。你更应该观察:

  1. token 类型是否对
  2. token 的值是否对
  3. 注释和空白是否真的被跳过去了
  4. ifreturn 这类词是否被识别成关键字,而不是普通标识符

只要这四类现象稳定,说明你的 lexer 已经具备把字符流交给 parser 的资格。

2.8 本章小结

词法分析器做的不是“理解程序含义”,而是先把连续字符整理成更适合继续推理的 token 流。它是语法分析的前门,没有这一步,后面的 AST 根本无从谈起。

2.9 下一步

现在去完成:

  1. Lab02 - step_2 词法分析器
  2. 观察 token 序列是否和讲义预期一致
  3. 然后继续进入 Chapter 3 — 语法分析器与 AST