世界は優しい
首页博客关于

Site

世界は優しい

世界很温柔,我们都在努力变得更好。

Navigation

  • 首页
  • 博客
  • 关于

Connect

  • GitHub
  • 作者

© 2026 ZZC. 本站内容以 CC BY-NC-SA 4.0 协议发布。

Built with Next.js · Tailwind CSS

Folders

课程介绍与评分Lec1: IntroductionLec2: Lexical AnalysisLec3: ParsingLec4: Abstract SyntaxLec5: Semantic AnalysisLec6: Activation RecordsLec7: Translate to Intermediate CodeLec8: Basic Blocks and TracesLec9: 指令选择Lec10: Liveness Analysis
Lec0: 课程介绍与成绩Lec1: IntroductionLec2: Operating-System StructuresLec3: ProcessesLec4: Threads(多线程编程)Lec5: CPU SchedulingLec6: 进程同步Lec7: DeadlocksLec8: Main MemoryLec9: Virtual MemoryLec10: File-System InterfaceLec11: File System ImplementationLec12: Mass-Storage System
Lec1: Basic Concepts in Reinforcement LearningLec2: Bellman EquationLec3: Bellman Optimality EquationLec4: Value Iteration & Policy IterationLec5: Monte Carlo Learning
首页
ManiGaussian 论文笔记ManiGaussian++ 论文笔记
AMP: 对抗动作先验替代复杂奖励函数DeepMimic: 从动作捕捉数据学习物理仿真角色技能DreamWaQ: 纯本体感知的四足鲁棒行走Imitating Animals: 从动物模仿到真实四足敏捷运动MoE-Loco: 多任务腿足运动的专家混合架构Multi-AMP: 多重对抗动作先验学习高级技能PIE: Proprioception with Imagination for ParkourRMA: Rapid Motor Adaptation for Legged Robots
论文阅读
Lec1: 五十音Lec2: 日语声调Lec3: 浊音和长音
Callout 语法速查
Hello World - 我的第一篇博客
Typora 语法兼容性测试
首页博客Coure-NotebookCompiler_PrincipleLec2: Lexical Analysis

编译原理

Lec2: Lexical Analysis

编译原理课程的第二章,介绍了词法分析的基本概念、正则表达式以及有限自动机。词法分析器将源代码转换为 token 流,使用正则表达式描述 token 模式,并通过 NFA 和 DFA 实现。课程还介绍了 Lex 工具,用于自动生成词法分析器,以及 DFA 最小化算法,优化状态数量。

2026 年 03 月 20 日/16 min read/ZZCZZC
#编译原理#课程笔记#计算机科学

Compiling Process

​ 编译器分为前端和后端:

  • 前端(Analysis)
    • Lexical Analysis: breaking the input into individual words or "tokens";
    • Syntax Analysis: parsing the phrase structure of the program;
    • Semantic Analysis: calculating the program's meaning.
  • 后端(Synthesis)
阶段内容
前端源代码 → IR
后端IR → 汇编/机器码

Task of lexical analyzer

image-20260303103527279

  • 输入:字符流(stream of characters)
  • 输出:token 流(stream of tokens)

​ 例如:

float match0(char *s)

​ 会被转换为:

FLOAT ID(match0) LPAREN CHAR STAR ID(s) RPAREN ...

Lexical Token

  • token:
    • 是字符的一个序列
    • 是语法中的一个基本单位
    • 有有限种 token 类型

​ 常见 token 类型:

类型示例
IDfoo, n14
NUM73, 082
REAL1.2, 5.5e-10
IFif
COMMA,
NOTEQ!=
LPAREN(
  • Reserved words: 不会用于 identifiers。例如:IF, VOID, RETURN

​ 非 token 例子:

类型示例
comment/* try again */
preprocessor directive#include<stdio.h>
preprocessor directive#define NUMS 5, 6
macroNUMS
blanks, tabs, and new-lines
  • The preprocessor deletes the non-tokens
    • Operates on the source character stream
    • Producing another character stream to the lexical analyzer
  • 预处理器先处理字符流,再交给词法分析器。

How to implement a lexical analyzer

词法规则(英文描述示例)

image-20260303104833367

Implementation

  • 四种方法:
    • An ad hoc lexer: 手写 lexer
    • Regular expressions: 描述 lexer
    • Deterministic finite automata: 实现 lexer
    • Mathematics: 结合 RE 和 DFA
Lexical Token Description→ManuallyRE→?DFA→Table-Driven ImplementationLexer\text{Lexical Token Description} \xrightarrow{\text{Manually}} \text{RE} \xrightarrow{\text{?}} \text{DFA} \xrightarrow{\text{Table-Driven Implementation}} \text{Lexer}Lexical Token DescriptionManually​RE?​DFATable-Driven Implementation​Lexer

Regular Expression

  • A language is a set of strings
  • A string is a finite sequence of symbols
  • A symbol is taken from a finite alphabet

基础元素

形式含义
a字符 a
ε空串

运算

  • M∣N:L(M∣N)={s∣s∈L(M)  or  s∈L(N)}M \mid N:L(M \mid N) = \{ s \mid s \in L(M) \; or \; s \in L(N) \}M∣N:L(M∣N)={s∣s∈L(M)ors∈L(N)}
  • M⋅N:L(M⋅N)={ st∣s∈L(M),  t∈L(N) }M \cdot N:L(M \cdot N) = \{\, st \mid s \in L(M),\; t \in L(N) \,\}M⋅N:L(M⋅N)={st∣s∈L(M),t∈L(N)}
  • M∗M*M∗,Kleene closure

​ 常见符号:

表达式含义
M+M 至少一次
M?0或1次
[abc]a | b | c
[a-z]字符范围
.任意字符
"abc"字符串字面量

Regular Expressions for Tokens

image-20260303111022753

Disambiguation rules

​ 有时规则匹配会有歧义冲突,有两种方式解决:

  • Longest Match(最长匹配):
    • 匹配最长可能字符串。
    • if8 匹配为 ID 而不是 IF + NUM
  • Rule Priority(规则优先级):
    • 若长度相同,优先匹配先写的规则
    • if 匹配为 IF 而不是 ID

Finite Automata

  • DFA 包含:
    • 有限状态集合
    • 唯一开始状态
    • 若干终止状态
    • 每个状态对每个字符只有一个转移
  • 接受规则
    • 从开始状态出发
    • 逐字符转移
    • 最终落在终止状态 → 接受

image-20260303111939210

多个 DFA 合并

  • 多个 token 自动机合并为一个大自动机:
    • 所有表达式的起点合并
    • 每个终态标记 token 类型

​ 例如:

image-20260303152553266

image-20260303152612668

  • 冲突时:
    • 采用最长匹配
    • 再按规则优先级

​ 例如在遇到 if 时:

image-20260303152709491

Transition matrix

​ 可以对 DFA 进行编码:

int edges[state][character];
int final[state];

​ final[state] 将 state numbers 映射至 actions,而 edges[state][character] 表示 DFA 的转移,如下:

image-20260303152938927

Recognizing the longest match

  • 使用两个变量:
    • LastFinal
    • InputPositionAtLastFinal
  • 当进入死状态时:
    • 回退到 last final
    • 输出 token

​ 使用以下标志符:

  • |:input position
  • ⊥: automaton position
  • T: Input position at last final

​ 展示一个示例:

if --not-a-com

​ 过程如下:

image-20260303160912398

NFA

​ DFA 的构造有些困难,可以考虑使用 NFA:

Description of lexical tokens (in natural language or in mind)→ManuallyRE→Thompson’s ConstructionNFA→Subset Construction, DFA MinimizationDeterministic Finite Automata→e.g., Table-Driven ImplementationLexer\text{Description of lexical tokens (in natural language or in mind)} \xrightarrow{\text{Manually}} \text{RE} \xrightarrow{\text{Thompson's Construction}} \text{NFA} \xrightarrow{\text{Subset Construction, DFA Minimization}} \text{Deterministic Finite Automata} \xrightarrow{\text{e.g., Table-Driven Implementation}} \text{Lexer}Description of lexical tokens (in natural language or in mind)Manually​REThompson’s Construction​NFASubset Construction, DFA Minimization​Deterministic Finite Automatae.g., Table-Driven Implementation​Lexer

NFA 定义

  • 同一字符可能有多条边
  • 可以有 ε 边(不消耗输入)
  • 接受规则:只要有一条路径能到终态就接受

From RE to Lexer

​ 词法分析器的完整构造流程:

Lexical Token Description→RENFA→Subset ConstructionDFA→Table ImplementationLexer\text{Lexical Token Description} \xrightarrow{\text{RE}} \text{NFA} \xrightarrow{\text{Subset Construction}} \text{DFA} \xrightarrow{\text{Table Implementation}} \text{Lexer}Lexical Token DescriptionRE​NFASubset Construction​DFATable Implementation​Lexer

​ 步骤说明:

阶段作用
RE描述 token 的模式
NFA使用 Thompson 构造
DFA使用 subset construction
Lexer表驱动实现

Regular Expression → NFA

​ 使用 Thompson Construction 将正则表达式转换为 NFA。

​ 特点:

  • 每个 RE 结构都有一个固定的 NFA 模板
  • 所有 NFA 只有一个入口和一个出口

基本构造

  1. 单字符

​ image-20260308144011398

  1. ε

image-20260308144110912

运算构造

  1. Union(M | N)

image-20260308144143710

  1. Concatenation(MN)

image-20260308144227078

  1. Kleene Star(M*)

image-20260308144300842

NFA Simulation

​ 直接运行 NFA 的基本思想:同时追踪多个可能状态。

​ 假设:

start state = s1
input = c1, c2, ... ck

​ 算法:

d ← closure({s1})
for i = 1 to k
    d ← DFAedge(d, ci)

​ 其中:

d = 当前可能状态集合

ε-closure

closure(S)

​ 定义:

  • 从集合 S 中所有状态出发,通过 ε-transitions 能到达的所有状态

​ 例:

1 --ε--> 2
1 --ε--> 3

​ 则:

closure({1}) = {1,2,3}

move(S, c)

move(S, c)

​ 表示:从 S 中的状态,通过字符 c 能到达的所有状态

​ 例如:

2 --a--> 4
3 --a--> 5
move({2,3}, a) = {4,5}

DFAedge(S, c)

​ 定义:

DFAedge(S, c) = closure(move(S, c))

​ 步骤:

  1. 计算 move
  2. 再计算 ε-closure

NFA Simulation Example

​ NFA:

1 --ε--> 2
1 --ε--> 3
2 --a--> 2
2 --b--> 4
3 --a--> 4
4 --b--> 5

​ 输入:

aab
  1. Step1:d0 = closure({1})、closure({1}) = {1,2,3}
  2. Step2:move({1,2,3}, a)、2 → 2, 3 → 4、d1 = {2,4}
  3. Step3:move({2,4}, a)、2 → 2、d2 = {2}
  4. Step4:move({2}, b)、2 → 4、d3 = {4}
  5. 判断:final state 为 5,但最终状态为 {4},所以字符串不能被接受。

Problem of NFA Simulation

​ 通过上面的例子可以发现:

Manipulating sets of states is expensive

也就是读取一个字符都需要经历 move、closure、set union 这三个步骤。

Solution: Subset Construction

​ 其中一种解决方法是:预先计算所有状态集合。即:

NFA→DFANFA \rightarrow DFANFA→DFA

​ 这样扫描输入时,有:

state = transition[state][char]

Subset Construction Algorithm

​ 核心思想为:DFA 的每个状态 = 一个 NFA 状态集合

  1. Step1:初始状态时,D1 = closure({start})
  2. Step2:对于每个状态集合 Di,对于每个输入符号 a ∈ Σ,计算 Dj = DFAedge(Di, a),若 Dj 为新集合,则加入 DFA 状态集合。
  3. Step3:重复:直到没有新集合产生

Example: NFA → DFA

​ NFA:

1 --ε--> 2
1 --ε--> 3
2 --a--> 2
2 --b--> 4
3 --b--> 3
3 --a--> 4
  • alphabet:Σ={a,b}\Sigma = \{a,b\}Σ={a,b}
  • 初始状态:closure({1})={1,2,3}closure(\{1\}) = \{1,2,3\}closure({1})={1,2,3},记为:D1

​ 则 DFA 转移如下:

Stateab
∅∅
  • DFA Final States 只要集合包含:NFA final state,则为 DFA final state。例如:{2,4}、{3,4}、{4}

DFA Minimization

​ DFA 最小化是将 DFA 化为最小形式(Minimized DFA)的过程,使得状态数最少且语言不变。

等价状态

  • 两个状态 p 和 q 等价,当且仅当:

    • 对于所有输入字符串 w,从 p 出发和从 q 出发,得到的输出结果相同
    • 即:δ(p,w)\delta(p, w)δ(p,w) 和 δ(q,w)\delta(q, w)δ(q,w) 要么都是接受状态,要么都是非接受状态
  • 两个状态 p 和 q 可区分,如果存在某个字符串 w 使得 δ(p,w)\delta(p, w)δ(p,w) 是接受状态而 δ(q,w)\delta(q, w)δ(q,w) 不是接受状态(反之亦然)

划分算法(Partitioning Algorithm)

  • 步骤:
    1. 初始划分:将状态分为接受状态组和非接受状态组
    2. 迭代细分:对每个组进行细分:
      • 如果组内两个状态对某个输入字符转移到不同组,则将它们分开
    3. 重复直到收敛:重复步骤 2 直到没有组可以再细分
    4. 合并:每个组压缩为一个状态

示例:DFA Minimization 详细过程

​ 给定以下 DFA(识别以 ab 结尾的字符串):

初始状态:0
接受状态:3

转移表:
状态  'a'  'b'
 0     1    2
 1     1    3
 2     1    2
 3     1    2

Step 1: 初始划分

  • 接受状态:{3}\{3\}{3}
  • 非接受状态:{0,1,2}\{0, 1, 2\}{0,1,2}

划分 P0={{0,1,2},{3}}P_0 = \{\{0,1,2\}, \{3\}\}P0​={{0,1,2},{3}}

Step 1 迭代 1:检查组 {0,1,2}\{0,1,2\}{0,1,2}

  • 对状态 0:
    • δ(0,′a′)=1∈\delta(0, 'a') = 1 \inδ(0,′a′)=1∈ 组 {0,1,2}\{0,1,2\}{0,1,2}
    • δ(0,′b′)=2∈\delta(0, 'b') = 2 \inδ(0,′b′)=2∈ 组 {0,1,2}\{0,1,2\}{0,1,2}
  • 对状态 1:
    • δ(1,′a′)=1∈\delta(1, 'a') = 1 \inδ(1,′a′)=1∈ 组 {0,1,2}\{0,1,2\}{0,1,2}
    • δ(1,′b′)=3∈\delta(1, 'b') = 3 \inδ(1,′b′)=3∈ 组 {3}\{3\}{3} ← 转移到不同组!
  • 对状态 2:
    • δ(2,′a′)=1∈\delta(2, 'a') = 1 \inδ(2,′a′)=1∈ 组 {0,1,2}\{0,1,2\}{0,1,2}
    • δ(2,′b′)=2∈\delta(2, 'b') = 2 \inδ(2,′b′)=2∈ 组 {0,1,2}\{0,1,2\}{0,1,2}

结论:状态 1 和 {0,2}\{0,2\}{0,2} 可区分,需要拆分!

划分 P1={{0,2},{1},{3}}P_1 = \{\{0,2\}, \{1\}, \{3\}\}P1​={{0,2},{1},{3}}

Step 1 迭代 2:检查组 {0,2}\{0,2\}{0,2}

  • 对状态 0:
    • δ(0,′a′)=1∈\delta(0, 'a') = 1 \inδ(0,′a′)=1∈ 组 {1}\{1\}{1}
    • δ(0,′b′)=2∈\delta(0, 'b') = 2 \inδ(0,′b′)=2∈ 组 {0,2}\{0,2\}{0,2}
  • 对状态 2:
    • δ(2,′a′)=1∈\delta(2, 'a') = 1 \inδ(2,′a′)=1∈ 组 {1}\{1\}{1}
    • δ(2,′b′)=2∈\delta(2, 'b') = 2 \inδ(2,′b′)=2∈ 组 {0,2}\{0,2\}{0,2}

结论:状态 0 和 2 行为完全相同,不需要拆分!

划分 P2={{0,2},{1},{3}}P_2 = \{\{0,2\}, \{1\}, \{3\}\}P2​={{0,2},{1},{3}}

Step 2: 构建最小化 DFA

  • 组 {0,2}\{0,2\}{0,2} → 新状态 A
  • 组 {1}\{1\}{1} → 新状态 B
  • 组 {3}\{3\}{3} → 新状态 C(接受状态)

转移计算:

  • 从 A(代表 {0,2}\{0,2\}{0,2})出发:

    • 读 'a':δ(0,′a′)=1∈B\delta(0,'a')=1 \in Bδ(0,′a′)=1∈B,所以 δ(A,′a′)=B\delta(A,'a') = Bδ(A,′a′)=B
    • 读 'b':δ(0,′b′)=2∈A\delta(0,'b')=2 \in Aδ(0,′b′)=2∈A,所以 δ(A,′b′)=A\delta(A,'b') = Aδ(A,′b′)=A
  • 从 B 出发:

    • 读 'a':δ(1,′a′)=1∈B\delta(1,'a')=1 \in Bδ(1,′a′)=1∈B,所以 δ(B,′a′)=B\delta(B,'a') = Bδ(B,′a′)=B
    • 读 'b':δ(1,′b′)=3∈C\delta(1,'b')=3 \in Cδ(1,′b′)=3∈C,所以 δ(B,′b′)=C\delta(B,'b') = Cδ(B,′b′)=C
  • 从 C 出发:

    • 读 'a':δ(3,′a′)=1∈B\delta(3,'a')=1 \in Bδ(3,′a′)=1∈B,所以 δ(C,′a′)=B\delta(C,'a') = Bδ(C,′a′)=B
    • 读 'b':δ(3,′b′)=2∈A\delta(3,'b')=2 \in Aδ(3,′b′)=2∈A,所以 δ(C,′b′)=A\delta(C,'b') = Aδ(C,′b′)=A

最小化结果

image-20260308192532557

最小化前状态数最小化后状态数
43

Different tokens 类型的情况

image-20260311210929281

  • state2 与 state4 是等价的,但是 A 和 B 是两种 token 类型,此时不应该放在一个等价类里面。
  • 考虑将他们分到不同的等价组里面,即可
    • Π={S−F,F1,…,Fk}\Pi=\{S-F,F_1,\dots,F_k\}Π={S−F,F1​,…,Fk​}

Lex: A Lexical Analyzer Generator

Lex 是一个 词法分析器生成器(lexer generator),用于根据 token 的正则表达式描述自动生成 lexer 程序。

它是 Unix 系统中经典的编译器工具,通常与 **Yacc / Bison(语法分析器生成器)**一起使用。

Motivation

手写词法分析器虽然可行,但存在以下问题:

  • 实现复杂
  • 代码容易出错
  • 维护困难
  • token 规则修改成本高

因此,可以使用 lexer generator 自动生成词法分析器。

Lex 工作流程

Lex 的基本流程如下:

也可以表示为:

Regular Expressions→LexLexer (C program)\text{Regular Expressions} \xrightarrow{\text{Lex}} \text{Lexer (C program)}Regular ExpressionsLex​Lexer (C program)

Lex Specification File

Lex 使用一个 .l 文件作为输入。

其结构分为 三部分:

definitions
%%
rules
%%
user code
  1. Definitions:用于定义:
    1. 宏(macros)
    2. 头文件
    3. 全局变量
    4. token 名称

例如:

%{
#include <stdio.h>
%}
 
DIGIT   [0-9]
ID      [a-zA-Z][a-zA-Z0-9]*

解释:

定义含义
%{ %}插入 C 代码
DIGIT宏定义
IDidentifier 的 RE
  1. Rules:规则部分格式:
pattern    action

pattern 是 正则表达式 action 是 C 代码

例如:

%%
"if"        return IF;
"while"     return WHILE;
{ID}        return ID;
{DIGIT}+    return NUM;
[ \t\n]+    ;
%%

解释:

规则含义
"if"关键字 if
{ID}标识符
{DIGIT}+数字
[ \t\n]+忽略空白
  1. User Code

用户自定义代码部分,例如:

int main() {
    yylex();
    return 0;
}

其中:

yylex()

是 Lex 自动生成的词法分析函数。

Example: Simple Lex Program

%{
#include <stdio.h>
%}
 
DIGIT   [0-9]
 
%%
{DIGIT}+   printf("NUMBER\n");
"+"        printf("PLUS\n");
"-"        printf("MINUS\n");
[ \t\n]    ;
%%
 
int main() {
    yylex();
}

输入:

12+3

输出:

NUMBER
PLUS
NUMBER

Lex 内部实现原理

Lex 内部实际上使用:

Regular Expressions
      ↓
     NFA
      ↓
     DFA
      ↓
Table-driven Scanner

这和前面讲的 RE → NFA → DFA → Lexer 完全一致。

Longest Match Rule

Lex 默认使用 Longest Match 匹配 最长可能字符串

例如:

== 
=

输入:

==

Lex 会选择:

==

而不是两个 =。

Rule Priority

如果多个规则匹配 相同长度字符串,使用 先写的规则

例如:

if      return IF;
[a-z]+  return ID;

输入:

if

结果:

IF

而不是 ID。

Reserved Words Handling

关键字通常写在 identifier 规则之前:

if      return IF;
else    return ELSE;
[a-z]+  return ID;

原因:

  • 同长度匹配
  • 使用 rule priority

Ignoring Tokens

有些 token 不需要返回,例如:

  • whitespace
  • comments

例如:

[ \t\n]+    ;

表示:

匹配但不执行任何动作

Interaction with Parser

Lex 通常和 Yacc/Bison 一起使用。

流程:

Source Program
      ↓
    Lexer
      ↓
    Tokens
      ↓
    Parser
      ↓
  Syntax Tree
  • Lex 负责 词法分析
  • Yacc 负责 语法分析
ZZC

Written by

ZZC
每天研究怎么摸鱼的神人

Comments

评论功能即将上线

On this page

  • Compiling Process
  • Task of lexical analyzer
  • Lexical Token
  • How to implement a lexical analyzer
  • 词法规则(英文描述示例)
  • Implementation
  • Regular Expression
  • 基础元素
  • 运算
  • Regular Expressions for Tokens
  • Disambiguation rules
  • Finite Automata
  • 多个 DFA 合并
  • Transition matrix
  • Recognizing the longest match
  • NFA
  • NFA 定义
  • From RE to Lexer
  • Regular Expression → NFA
  • 基本构造
  • 运算构造
  • NFA Simulation
  • ε-closure
  • move(S, c)
  • DFAedge(S, c)
  • NFA Simulation Example
  • Problem of NFA Simulation
  • Solution: Subset Construction
  • Subset Construction Algorithm
  • Example: NFA → DFA
  • DFA Minimization
  • 等价状态
  • 划分算法(Partitioning Algorithm)
  • 示例:DFA Minimization 详细过程
  • Step 1: 初始划分
  • Step 1 迭代 1:检查组 $\{0,1,2\}$
  • Step 1 迭代 2:检查组 $\{0,2\}$
  • Step 2: 构建最小化 DFA
  • 最小化结果
  • Different tokens 类型的情况
  • Lex: A Lexical Analyzer Generator
  • Motivation
  • Lex 工作流程
  • Lex Specification File
  • Example: Simple Lex Program
  • Lex 内部实现原理
  • Longest Match Rule
  • Rule Priority
  • Reserved Words Handling
  • Ignoring Tokens
  • Interaction with Parser