分词和词法分析
分词(tokenizing)和词法分析(Lexing)之间的区别是非常微妙、晦涩的,主要差异在于词法单元的识别是通过有状态还是无状态的方式进行的。
🔹 什么是“分词(tokenizing)”?
在自然语言处理(NLP)或编程语言处理中,分词就是把一段文本或代码切分成一个个有意义的最小单元(词/符号),比如:
int a = 100;
分词后可能是:
[int] [a] [=] [100] [;]
它只关心把字符流拆开,通常是简单、线性的处理。
🔹 什么是“词法分析(lexing)”?
词法分析是编译器前端的一部分,它不仅要切分,还要识别每一段的类型(比如变量名、关键字、运算符等),而且这过程中可能需要记住上下文状态。
例如,词法分析器可能需要记住:
- 当前是否处于字符串中?
- 是否刚刚识别了转义符?
- 处于模板语言的哪一段?
🔹 什么叫“有状态”与“无状态”?
• 无状态(Stateless):每次只根据当前输入决定操作,不记住前后文,比如简单的空格分词。
• 有状态(Stateful):需要记录某些信息决定下一步,比如是否在字符串内部,是否进入注释等。
分词往往可以是无状态的过程。
词法分析则通常是有状态的,需要根据当前状态决定怎么识别下一个词法单元(token)。
🌟 示例代码
printf("Hello, world! // this is not a comment");
🔹 无状态的分词(Tokenizing)
无状态分词可能会简单地按空格或符号来切分:
[printf] ["("] ["Hello,] [world!] [//] [this] [is] [not] [a] [comment"] [")"] [";"]
这里的问题:
- 它没理解 “Hello, world! // this is not a comment” 是一个完整的字符串;
- 它误把字符串内部的内容当作注释(//)开头了;
- 没有考虑上下文状态(是否在字符串里)。
🔸 有状态的词法分析(Lexing)
词法分析器则会维护“状态”:
- 当前是否在字符串内;
- 是否进入注释;
- 是否遇到转义字符等。
处理结果可能是:
[printf] [ ( ] [STRING_LITERAL: "Hello, world! // this is not a comment"] [ ) ] [ ; ]
它知道:
- 一旦遇到引号 ",就进入字符串状态;
- 直到下一个匹配的 " 才算结束;
- 所以不会把字符串里的 // 当作注释。
✅ 总结
项目 | 分词(Tokenizing) | 词法分析(Lexing) |
---|---|---|
是否有状态 | 否(stateless) | 是(stateful) |
能力 | 机械切分字符 | 识别 token + 上下文理解 |
举例区别 | “a // b” 切成多个部分 | 整个字符串 “a // b” 被识别为一个 STRING_LITERAL |
抽象语法树
抽象语法树(Abstract Syntax Tree,简称 AST)是编程语言处理中的一个核心概念,它是一种树形结构,用于表示程序的语法结构的抽象层次。
🔹 用简单的比喻解释 AST
想象你在写一句话:
1 + 2 * 3
人一看就知道:
• 2 * 3 应该先算,
• 然后再加上 1
但计算机需要一种结构来表达这种优先级和结构关系,这就用到了 AST。
🔸 把 1 + 2 * 3 转换为 AST:
这个表达式的 AST 长这样:
+
/ \
1 *
/ \
2 3
这个结构明确表达了:
• 先计算 2 * 3
• 再把结果加上 1
🔹 AST 的特点
• 抽象:不包括所有原始代码细节(比如括号、分号),只保留语义必要的部分。
• 结构化:每个节点代表一个语法元素,比如变量、操作符、函数调用等。
• 通用性强:编译器、解释器、静态分析器、代码格式化器等都可以用 AST 来理解和处理代码。
🔸 更复杂的例子(函数)
function add(a, b) {
return a + b;
}
AST 大概会表示为:
FunctionDeclaration
├── name: "add"
├── parameters: ["a", "b"]
└── body:
└── ReturnStatement
└── BinaryExpression
├── left: Identifier("a")
├── operator: "+"
└── right: Identifier("b")
✅ 总结
抽象语法树(AST)是一种树形数据结构,用来抽象地表示代码的语法结构,让计算机能够“理解”代码的组成和逻辑。