Skip to content

Repository files navigation

编译原理实验—— C--语言编译器

邓开圣 161220031

lab1: 词法分析、语法分析

实验内容

使用C语言编写程序,对使用C--语言书写的源代码进行词法分析和语法分析,并输出语法树。

实现思路

  • 借助词法分析工具Flex和语法分析工具GNU Bison
  • Flex使用用户定义的正则表达式对词素进行匹配,Bison根据用户定义的语法规则对识别的词法单元进行归约
  • 每进行一次归约,定义构造语法树的动作,直到语法分析过程结束,得到一棵完整的语法树
  • 关键数据结构与算法:

1592880529314

语法树节点结构体

1592880625338

创建语法树节点

1592880674721

根据父子关系构建语法树

输出示例

// a3.cmmintmain(){
int a, b;
int result;
int start;
int found = 0;
a = read();
b = read();
if(a > b){
start = a;
}else {
start = b;
}
while(found == 0){
if(start == (start / a) * a){
if(start == (start / b) * b){
result = start;
found = 1;
}else {
start = start + 1;
}
}else {
start = start + 1;
}
}
write(result);
return0;
}
// a3.outProgram (1)
ExtDefList (1)
ExtDef (1)
Specifier (1)
TYPE: int
FunDec (1)
ID: main
LP
RP
CompSt (1)
LC
DefList (2)
Def (2)
Specifier (2)
TYPE: int
DecList (2)
Dec (2)
VarDec (2)
ID: a
COMMA
DecList (2)
Dec (2)
VarDec (2)
ID: b
SEMI
DefList (3)
Def (3)
Specifier (3)
TYPE: int
DecList (3)
Dec (3)
VarDec (3)
ID: result
SEMI
DefList (4)
Def (4)
Specifier (4)
TYPE: int
DecList (4)
Dec (4)
VarDec (4)
ID: start
SEMI
DefList (5)
Def (5)
Specifier (5)
TYPE: int
DecList (5)
Dec (5)
VarDec (5)
ID: found
ASSIGNOP
Exp (5)
INT: 0
SEMI
StmtList (6)
Stmt (6)
Exp (6)
Exp (6)
ID: a
ASSIGNOP
Exp (6)
ID: read
LP
RP
SEMI
StmtList (7)
Stmt (7)
Exp (7)
Exp (7)
ID: b
ASSIGNOP
Exp (7)
ID: read
LP
RP
SEMI
StmtList (8)
Stmt (8)
IF
LP
Exp (8)
Exp (8)
ID: a
RELOP
Exp (8)
ID: b
RP
Stmt (8)
CompSt (8)
LC
StmtList (9)
Stmt (9)
Exp (9)
Exp (9)
ID: start
ASSIGNOP
Exp (9)
ID: a
SEMI
RC
ELSE
Stmt (10)
CompSt (10)
LC
StmtList (11)
Stmt (11)
Exp (11)
Exp (11)
ID: start
ASSIGNOP
Exp (11)
ID: b
SEMI
RC
StmtList (13)
Stmt (13)
WHILE
LP
Exp (13)
Exp (13)
ID: found
RELOP
Exp (13)
INT: 0
RP
Stmt (13)
CompSt (13)
LC
StmtList (14)
Stmt (14)
IF
LP
Exp (14)
Exp (14)
ID: start
RELOP
Exp (14)
Exp (14)
LP
Exp (14)
Exp (14)
ID: start
DIV
Exp (14)
ID: a
RP
STAR
Exp (14)
ID: a
RP
Stmt (14)
CompSt (14)
LC
StmtList (15)
Stmt (15)
IF
LP
Exp (15)
Exp (15)
ID: start
RELOP
Exp (15)
Exp (15)
LP
Exp (15)
Exp (15)
ID: start
DIV
Exp (15)
ID: b
RP
STAR
Exp (15)
ID: b
RP
Stmt (15)
CompSt (15)
LC
StmtList (16)
Stmt (16)
Exp (16)
Exp (16)
ID: result
ASSIGNOP
Exp (16)
ID: start
SEMI
StmtList (17)
Stmt (17)
Exp (17)
Exp (17)
ID: found
ASSIGNOP
Exp (17)
INT: 1
SEMI
RC
ELSE
Stmt (18)
CompSt (18)
LC
StmtList (19)
Stmt (19)
Exp (19)
Exp (19)
ID: start
ASSIGNOP
Exp (19)
Exp (19)
ID: start
PLUS
Exp (19)
INT: 1
SEMI
RC
RC
ELSE
Stmt (21)
CompSt (21)
LC
StmtList (22)
Stmt (22)
Exp (22)
Exp (22)
ID: start
ASSIGNOP
Exp (22)
Exp (22)
ID: start
PLUS
Exp (22)
INT: 1
SEMI
RC
RC
StmtList (25)
Stmt (25)
Exp (25)
ID: write
LP
Args (25)
Exp (25)
ID: result
RP
SEMI
StmtList (26)
Stmt (26)
RETURN
Exp (26)
INT: 0
SEMI
RC

lab2: 语义分析

实验内容

  • 在词法分析和语法分析程序的基础上编写一个程序,对C--代码进行语义分析和类型检查,并打印分析结果。
  • 需要识别的错误类型:
    • 错误类型1: 变量在使用时未经定义。
    • 错误类型2: 函数在调用时未经定义。
    • 错误类型3: 变量出现重复定义,或变量与前面定义过的结构体名字重复。
    • 错误类型4: 函数出现重复定义(即同样的函数名出现了不止一次定义) 。
    • 错误类型5: 赋值号两边的表达式类型不匹配。
    • 错误类型6: 赋值号左边出现一个只有右值的表达式。
    • 错误类型7: 操作数类型不匹配或操作数类型与操作符不匹配(例如整型变量与数组变量相加减,或数组(或结构体)变量与数组(或结构体)变量相加减)。
    • 错误类型8: return语句的返回类型与函数定义的返回类型不匹配。
    • 错误类型9: 函数调用时实参与形参的数目或类型不匹配。
    • 错误类型10: 对非数组型变量使用“[…]” (数组访问)操作符。
    • 错误类型11: 对普通变量使用“(…)”或“()” (函数调用)操作符。
    • 错误类型12: 数组访问操作符“[…]” 中出现非整数(例如a[1.5])。
    • 错误类型13: 对非结构体型变量使用“.” 操作符。
    • 错误类型14: 访问结构体中未定义过的域。
    • 错误类型15: 结构体中域名重复定义(指同一结构体中) ,或在定义时对域进行初始化(例如struct A { int a = 0; })。
    • 错误类型16: 结构体的名字与前面定义过的结构体或变量的名字重复。
    • 错误类型17: 直接使用未定义过的结构体来定义变量。

实现思路

  • 前序遍历上一步生成的语法树,在遍历过程中生成语义信息,同时检查语义是否合法
  • 主要数据结构有3个:哈希表、符号表、类型表。其中哈希表用于检查某个符号当前是否已经定义;符号表为链表结构,用于获取当前已经定义的变量或函数的信息;类型表也为链表结构,用于获取当前已经定义的类型信息。
  • 关键数据结构与算法:

1592886171168

哈希表

1592886247938

变量信息描述符

1592886918975

类型信息描述符

只有复杂类型(数组、函数和结构体)会用到typeDetail字段

1592886944966

数组、结构体和函数的描述符

输出示例

// a1.cmmintmain(int x1, int x2){
int p = x1;
int i = 3;
int k = x2 + p;
i = x1 + x3;
return k;
}

1592887431723

// a2.cmmstructStudent{
int id;
int weight;
int grades;
};
intnewStudent(int a, int b){
structStudent st;
st.id = a;
st.weight = b;
st.grades = 0;
return0;
}
intmain(){
int k = 3;
int p = 14;
newStdent(k, p);
return0;
}

1592897243413

// b1.cmmstructLeaf {
int number;
int isGreen;
};
structTree {
structLeaf leaves[100];
int height;
float weight;
int hasFruit;
};
structAppleTree {
structTree t;
int numberApple;
float priceApple;
int highQuality;
};
structAppleTreenewAppleTree() {
int i = 0;
int x = 1;
structAppleTree at = x();
structTree tt;
structLeaf sto[100];
while(i < 100){
sto[i].number = x;
tt.leaves[i].number = sto[i].number;
sto[i].isGreen = 1;
tt.leaves[i].isGreen = sto[i].isGreen;
i = i + 1;
}
tt.height = 100;
tt.weight = 2.5;
tt.hasFruit = 0;
at.numberApple = 1;
at.priceApple = 1.2;
i = 0;
while(i < 100){
at.t[i].leaves[i].number = tt.leaves[i].number;
at.t.leaves[i].isGreen = tt.leaves[i].isGreen;
i = i + 1;
}
at.t.height = tt.height;
at.t.weight = tt.weight;
at.hasFruit = 1;
at.highQuality = at.t.hasFruit * at.t.weight;
return at;
}
intmain(){
structAppleTree aat = newAppleTree();
return0;
}

1592897735500

lab3: 中间代码生成(与优化)

实验内容

在词法分析、语法分析和语义分析的基础上,将C--源代码翻译为三地址代码形式的中间代码。

1592898025919

中间代码的形式及操作规范

实现思路

  • 前序遍历语法树,过程类似语义分析,遍历到特定位置时根据当前使用的产生式创建中间代码

1592899004532

执行中间代码生成的一系列translate函数
  • 关键数据结构与算法:

1592899748497

中间代码的种类,用于在输出时设置不同的语句形式

1592899790160

操作数,一开始为了实现比较好的可拓展性用了一个结构体,后来发现只需要一个char*就行了,但保 留了这个结构体

1592899837797

中间代码描述符,包含了操作数、操作符、操作符数量、中间代码类型等成员

1592899877067

生成的中间代码用单向链表组织

1592899915723

以上几个数据结构分别表示源代码与中间代码的变量名映射表节点、数组描述符节点和特殊参数节点, 均用单向链表组织

输出示例

// a3.cmm// 求a, b的最小公倍数intmain(){
int a, b;
int result;
int start;
int found = 0;
a = read();
b = read();
if(a > b){
start = a;
}else {
start = b;
}
while(found == 0){
if(start == (start / a) * a){
if(start == (start / b) * b){
result = start;
found = 1;
}else {
start = start + 1;
}
}else {
start = start + 1;
}
}
write(result);
return0;
}
FUNCTION main :
found := #0READ t1
a := t1
READ t3
b := t3
IF a > b GOTO label0
GOTO label1
LABEL label0 :
start := a
GOTO label2
LABEL label1 :
start := b
LABEL label2 :
IF found == #0GOTO label4
GOTO label5
LABEL label4 :
t7 := start / a
t6 := t7 * a
IF start == t6 GOTO label6
GOTO label7
LABEL label6 :
t9 := start / b
t8 := t9 * b
IF start == t8 GOTO label9
GOTO label10
LABEL label9 :
result := start
found := #1GOTO label2
LABEL label10 :
t13 := start + #1
start := t13
GOTO label2
LABEL label7 :
t15 := start + #1
start := t15
GOTO label2
LABEL label5 :
t17 := result
WRITE t17
RETURN #0

1592900257113

在基于PyQt4的模拟器上运行中间代码

lab4: 目标代码生成

实验内容

在词法分析、语法分析、语义分析和中间代码生成的基础上,将C--源代码翻译为MIPS32指令序列(可以包含伪指令),并在SPIM Simulator上运行。

实现思路

  • 关键数据结构和算法

本实验使用的主要数据结构有如下3个: VarDesc 表示变量描述符, op 表示其内容(操作数), reg_no 表示其存储的寄存器的下标; Register 是寄存器描述符,包含寄存器内容、寄存器名字、寄 存器索引以及寄存器当前年龄(用于寄存器分配),后续介绍寄存器分配使用的算法。 StackDesc 是 栈描述符,用数组模拟一个在内存中的栈结构,用于函数调用的目标代码生成。

1592900648135

关键的函数有两个: writeAssemblyCodegetRegwriteAssemblyCode 用于将传入的中间代码翻译成目标代码并输出到目标文件中。主要思想是用 switch ... case... 语句根据中间代码的类型分别处理。 getReg 函数用于分配寄存器,返回分配的寄存器的索引。主要思想是:首先遍历所有可用寄存器看当 前操作数是否已经分配到了某个寄存器中,若是,则直接返回该寄存器的索引;若否,先给每个当前已 经已有内容的寄存器老化(即 age +1),然后寻找是否有空寄存器,若有,则直接使用找到的第一个空 寄存器;若无,则找当前 age 值最大的一个寄存器,将其内容换出,使用这个寄存器存储参数中的操作 数。并且每次在访问某个寄存器中都要将该寄存器的 age 置为0,这样每次换出的都是最近最不常使用 的操作数

1592900770012

输出示例

@ a3.cmm 生成的目标代码.data_prompt: .asciiz "Enter an integer:"_ret: .asciiz "\n".globl main.textread: li$v0,4 la$a0, _promptsyscall li$v0,5syscall jr$rawrite: li$v0,1syscall li$v0,4 la$a0, _retsyscall move$v0,$0 jr$ramain: li$t0,0 addi$sp,$sp,-4 sw$ra,0($sp) jal read lw$ra,0($sp) addi$sp,$sp,4 move$t1,$v0 move$t2,$t1 addi$sp,$sp,-4 sw$ra,0($sp) jal read lw$ra,0($sp) addi$sp,$sp,4 move$t3,$v0 move$t4,$t3 bgt$t2,$t4, label0 j label1label0: move$t5,$t2 j label2label1: move$t5,$t4label2: li$t6,0 beq$t0,$t6, label4 j label5label4:div$t5,$t2 mflo$t7mul$s0,$t7,$t2 beq$t5,$s0, label6 j label7label6:div$t5,$t4 mflo$s1mul$s2,$s1,$t4 beq$t5,$s2, label9 j label10label9: move$s3,$t5 li$t0,1 j label2label10: addi$s4,$t5,1 move$t5,$s4 j label2label7: addi$s5,$t5,1 move$t5,$s5 j label2label5: move$s6,$s3 move$a0,$s6 addi$sp,$sp,-4 sw$ra,0($sp) jal write lw$ra,0($sp) addi$sp,$sp,4 move$v0,$0 jr$ra

1592900960790

在SPIM Simulator上的运行结果

About

Compiler principles and technology project, Nanjing University, spring 2020.

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages