编译原理中的词法分析器:形式化视角
1. 词法分析器概述
词法分析器(Lexical Analyzer)是编译器的第一个阶段,负责将源代码字符流转换为有意义的词素(Lexeme)序列,并生成对应的词法单元(Token)。这个过程类似于人类阅读时将连续的字母组合识别为单词的过程。
形式化定义
给定字母表 Σ,词法分析器实现的是一个映射函数:
makefile
L: Σ* → (TokenClass, Lexeme)*
其中: • Σ* 表示所有可能的字符序列 • TokenClass 是预定义的语法类别(如标识符、关键字、运算符等) • Lexeme 是源代码中匹配特定模式的字符序列
2. 词法分析器的数学模型
词法分析器可以建模为一个有限自动机(Finite Automaton),具体来说是一个确定有限自动机(DFA):
css
M = (Q, Σ, δ, q₀, F)
其中: • Q: 有限状态集 • Σ: 输入字母表 • δ: 转移函数 Q × Σ → Q • q₀ ∈ Q: 初始状态 • F ⊆ Q: 接受状态集
示例:识别整数的DFA
解释: • q0: 初始状态 • q1: 接受状态(识别出整数) • q2: 错误状态(非数字开头) • q3: 接受状态(识别出整数后遇到非数字字符)
3. 正则表达式到DFA的转换
词法分析器通常基于正则表达式定义词法规则。转换过程如下:
- 正则表达式 → NFA(非确定有限自动机)
- NFA → DFA(子集构造法)
- DFA最小化
示例:标识符的正则表达式
标识符通常定义为:字母开头,后跟字母或数字
正则表达式:
css
[a-zA-Z][a-zA-Z0-9]*
对应的NFA:
转换为DFA后:
4. 词法分析算法
最长匹配原则算法
lua
function Lex():
start ← 0
state ← q₀
last_accept ← -1
accept_pos ← -1
for i from 0 to input.length:
char ← input[i]
if δ(state, char) is defined:
state ← δ(state, char)
if state ∈ F:
last_accept ← i
accept_pos ← i
else:
break
if last_accept ≠ -1:
return (TokenClass, input[start..last_accept])
else:
return ERROR
示例分析
输入字符串:"count123+5"
处理过程:
- 识别"count123"为标识符
- 识别"+"为运算符
- 识别"5"为整数
5. 冲突解决与优先级
当多个模式可以匹配同一输入时,需要解决冲突:
- 最长匹配优先
- 相同长度时,按规则定义的优先级
形式化定义
设模式集合 P = {p₁, p₂, ..., pₙ},每个模式 pᵢ 对应一个优先级 rᵢ。
对于输入字符串 s,词法分析器选择使得 |s'| 最大且 rᵢ 最高的 (pᵢ, s'),其中 s' 是 s 的前缀且 s' ∈ L(pᵢ)。
6. 实际应用示例
考虑一个简单的编程语言,定义以下词法规则:
ini
digit = [0-9]
letter = [a-zA-Z]
id = letter(letter|digit)*
num = digit+
relop = < | > | <= | >= | == | !=
对应的DFA需要合并所有这些规则,形成统一的状态转移图。
7. 词法分析器的实现方式
- 手工编码:直接实现状态转移表
- 生成器工具:如Lex/Flex,输入正则表达式规则,自动生成分析器
Flex规则示例
kotlin
%%
[0-9]+ { return NUMBER; }
[a-zA-Z]+ { return IDENTIFIER; }
"+" { return PLUS; }
[ \t\n] ; /* 忽略空白 */
. { return ERROR; }
%%
8. 数学性质验证
词法分析器的正确性可以通过以下性质验证:
- 完备性:∀s ∈ L(G), ∃t ∈ Token*, 使得 Lex(s) = t
- 确定性:∀s ∈ Σ*, Lex(s) 是唯一的
- 无歧义:∀s ∈ Σ*, 不存在 s = xy = zw 使得 x ≠ z 且 x, z ∈ L(P)
其中 G 是词法文法,P 是模式集合。
9. 复杂度分析
• 时间复杂度:O(n),其中 n 是输入长度 • 空间复杂度:O(1),仅需要常数空间存储状态
这个线性复杂度是词法分析器高效的关键原因,使得它能够快速处理大型源文件。