3.1 词法单元(token)、词素(lexeme)、模式(pattern)与词法单元流
词法分析是第一个把原始源码文本转换成结构化表示的编译阶段。它的输入是一串字符,输出是一串词法单元。听起来很简单,但这个边界是非常重要的工程契约:后续前端阶段都依赖词法分析器(lexer)保留足够信息,用于语法分析、错误诊断、格式化、IDE 功能和源码映射(source mapping)。
词法单元种类 是类别,例如 IDENT、INT、IF、PLUS、LPAREN 或 EOF。词素(Lexeme) 是某个词法单元在源码中匹配到的具体子串。模式(Pattern) 描述某类词法单元可以接受的词素集合。一个 词法单元 通常是把种类(kind)、词素、源码位置和额外元数据组合起来的记录。
例如在:
let total = price + tax;词素 total 可能产生词法单元 IDENT("total"),而词素 let 可能产生词法单元 LET("let")。它们都是字母串,但词法分析器策略把一个分类成关键字(keyword),把另一个分类成标识符(identifier)。这个策略必须确定,因为语法分析器(parser)不会重新解释原始字符。
词法单元记录
生产级词法单元记录通常不止包含种类(kind):
type Token = {
kind: TokenKind;
lexeme: string;
startOffset: number;
endOffset: number;
line: number;
column: number;
channel: "default" | "trivia" | "error";
};kind 是语法分析器主要消费的信息。lexeme 保留原始文本。偏移量(offset)支持工具和源码映射。行(line)与列(column)支持面向人的诊断。通道(channel)表示词法单元是否对语法分析器可见,是否是空白/注释这样的附属内容(trivia),或者是否是错误词法单元。
如果词法分析器只输出词法单元种类,对于玩具语法分析器可能足够,但对认真设计的编译器远远不够。如果词法分析器丢弃偏移量,诊断就无法精确标出坏字符。如果词法分析器直接丢弃注释,格式化器(formatter)就无法保留注释。如果词法单元不知道自己的范围,IDE 也很难实现“定位光标下词法单元”。
模式定义词法单元语言
每个词法单元种类都有一门语言:所有可能产生该词法单元的词素集合。有些语言是有限的。LET 可能就是 {let}。PLUS 可能就是 {+}。其他语言是无限的。IDENT 可能是所有以字母或下划线开头,并继续由字母、数字或下划线组成的串。INT 可能是所有非空数字序列。
一个小语言的典型词法单元 pattern:
IDENT = [A-Za-z_][A-Za-z0-9_]*
INT = [0-9]+
FLOAT = [0-9]+ "." [0-9]+
STRING = "..." with escape handling
WS = [ \t\r\n]+形式化的正则表达式(regex)可以描述大多数形状,但实现仍然要携带策略。是否允许 Unicode 标识符?是否允许前导零?2. 是 FLOAT(2.),还是 INT(2) 后跟 DOT?注释是跳过还是作为附属内容保留?这些是语言设计决定,不是偶然行为。
词法单元流是阶段边界
词法分析器与语法分析器的边界越稳定越好。语法分析器不应该关心注释如何识别、转义序列(escape sequence)如何扫描、词法分析器是手写还是生成。它应该接收稳定的词法单元流,并根据词法单元种类和值(value)做语法判断。
对于源码:
if x == 10 // ok一个有用的默认通道(default-channel)词法单元流可能是:
IF "if" line 1, col 1
IDENT "x" line 1, col 4
EQEQ "==" line 1, col 6
INT "10" line 1, col 9
EOF "" line 1, col 17空白和行注释可以对语法分析器跳过,但它们不一定应该从整个编译器生态中消失。格式化器、重构工具(refactoring tool)、文档抽取器(documentation extractor)和 IDE 经常需要附属内容。因此很多编译器会保留语法分析器可见的词法单元和附属内容的关联流,或者把前导/尾随附属内容绑定到附近词法单元上。
EOF 也值得特别注意。它不是源文件中的字符,但它是语法分析器需要的哨兵词法单元。它应该携带位置,这样语法分析器才能报告像“expected } before end of file”这样的精确消息。
词法单元是阶段契约,不只是字符串
可靠词法单元通常有 kind、原始 lexeme、半开源码范围(source range)[start,end)、行/列或行映射(line map),以及可选的解码值(decoded value)。对 "a\\n",词素保留引号和转义拼写,解码值则是两个字符 a 与换行符;两者都保留,诊断(diagnostic)和语义层才不会争抢“词法单元文本”的含义。
文法(grammar)只看种类,后续层再用 payload。INT("0042", value=42) 让语法分析器只需识别 INT,格式化器/警告仍能知道原写法。EOF 应是文件末尾的零宽词法单元,不应是 null;这样语法分析器接受与未闭合结构(unterminated structure)的错误处理一致。
词法单元流不变量
- 范围单调且不重叠,合成恢复词法单元(synthetic recovery token)除外。
- 空白/注释要么一致丢弃,要么一致作为附属内容发出。
- 每次词法分析器迭代至少消费一个源码单元,或返回 EOF/error,避免挂起。