用lex和yacc写成的一个具有解析类C语言的编译器,能够解析基础的C语言文法,并生成相应的语法树,通过语义分析形成中间代码。
编译的功能:
编译流程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qcpVBYrM-1645692610654)(report/img/trans_pro.png)]
画代码结构调用关系图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GRAQuiHz-1645692610656)(report/img/code.png)]
环境要求:flex bison g++11 python3
词法分析器是从左到右逐个字符地对源程序进行扫描,产生一个个的单词符号,把作为字符串的源程序改造成为单词符号串的中间程序。词法分析器部分由Lex实现。
先列实现的常量数据类型:eg. void,int,double,char
类别 | 内容 |
---|---|
D(数字) | [0-9] |
L(字母) | [a-zA-Z] |
H(16进制) | [a-fA-F0-9] |
E(科学计数表示) | [Ee][±]?{D}+ |
P | ([Pp][±]?{D}+) |
FS | (f|F|l|L) |
标识符 | {L}({L}|{D})* |
整数常量 | [1-9][0-9]* |
浮点数常量 | [0-9]+.[0-9]+ |
字符串常量 | L?"(\\.|[^\\"\n]*" |
常量 | L?.(\\.|[^\\’\n])+’ |
常量 | 0[xX]{H}+{P}{FS}? |
常量 | 0[xX]{H}*"."{H}+{P}?{FS}? |
常量 | 0[xX]{H}+"."{H}*{P}?{FS}? |
正则表达式定义:
{L}({L}|{D})* {count(); yylval.gt =create_tree("IDENTIFIER",0,yylineno); return(check_type());/*IDENTIFIER*/ }
[1-9][0-9]* { count(); yylval.gt =create_tree("CONSTANT_INT",0,yylineno); return(CONSTANT_INT); /*10进制*/}
L?'(\\.|[^\\'\n])+' { count(); return(CONSTANT); }
[0-9]+\.[0-9]+ { count(); yylval.gt =create_tree("CONSTANT_DOUBLE",0,yylineno); return(CONSTANT_DOUBLE); /*浮点数*/}
0[xX]{H}+{P}{FS}? { count(); return(CONSTANT); }
0[xX]{H}*"."{H}+{P}?{FS}? { count(); return(CONSTANT); }
0[xX]{H}+"."{H}*{P}?{FS}? { count(); return(CONSTANT); }
"/*" { comment();/*注释*/}
"//"[^\n]* { /* consume //-comment */ }
L?\"(\\.|[^\\"\n])*\" { count(); yylval.gt =create_tree("STRING_LITERAL",0,yylineno); return(STRING_LITERAL); /*字符串常量*/
名称 |
---|
bool, char, int, double, void |
continue, break, do, while, for |
if, else |
goto, return |
return |
true, false |
先列实现的操作符类型
类别 | 内容 |
---|---|
单目运算符 | + - ~ ! |
关系运算符 | < > <= >= != == |
算术运算符 | + - * / % | ^ & |
逻辑运算符 | || && |
移位运算 | >> << |
赋值运算符 | = += -= *= /= %= &= ^= |= <<= >>= |
注释表示: // /* */
"/*" { comment();/*注释*/}
"//"[^\n]* { /* consume //-comment */ }
[\t\v\n\f ] { count();}
语法分析是编译过程的核心部分。它的任务是在词法分析识别出单词符号串的基础上,分析并判定程序的语法结构是否符合语法规则。语法分析部分由YACC在基于我们定义的树的结构的基础上实现。
CFG流程图:
Program -> translation_unit
translation_unit -> external_declaration | translation_unit external_declaration
statement -> **compound_statement **| expression_statement| selection_statement| iteration_statement| jump_statement
**compound_statement ** -> ‘{’ ‘}’ | ‘{’ block_item_list ‘}’
block_item_list -> block_item | block_item_list block_item
block_item* -> declaration | statement
expression_statement -> ‘;’ | expression ‘;’
selection_statement -> IF ‘(’ expression ‘)’ statement %prec LOWER_THAN_ELSE | IF ‘(’ expression ‘)’ statement ELSE statement
iteration_statement -> WHILE ‘(’ expression ‘)’ statement| DO statement WHILE ‘(’ expression ‘)’ ‘;’| FOR ‘(’ expression_statement expression_statement ‘)’ statement| FOR ‘(’ expression_statement expression_statement expression ‘)’ statement| FOR ‘(’ declaration expression_statement ‘)’ statement| FOR ‘(’ declaration expression_statement expression ‘)’ statement
jump_statement ->CONTINUE ‘;’| BREAK ‘;’| RETURN ‘;’| RETURN expression ‘;’
external_declaration -> function_definition | declaration
function_definition -> type_definition declarator declaration_list compound_statement| type_definition declarator compound_statement
declaration_list -> declaration | declaration_list declaration
declaration -> type_definition ‘;’| type_definition init_declarator_list ‘;’
type_definition -> VOID | CHAR | INT | DOUBLE | BOOL
init_declarator_list ->init_declarator| init_declarator_list ‘,’ init_declarator
init_declarator ->declarator| declarator ‘=’ initializer
declarator ->IDENTIFIER| ‘(’ declarator ‘)’| declarator ‘[’ assignment_expression ‘]’| declarator ‘[’ ‘*’ ‘]’| declarator ‘[’ ‘]’| declarator ‘(’ parameter_list ‘)’| declarator '('identifier_list ‘)’| declarator ‘(’ ‘)’
initializer ->assignment_expression| ‘{’ initializer_list ‘}’| ‘{’ initializer_list ‘,’ ‘}’
initializer_list ->initializer| designation initializer| initializer_list ‘,’ initializer| initializer_list ‘,’ designation initializer
designation -> designator_list
designator_list -> designator | designator_list designator
designator -> ‘[’ logical_or_expression ‘]’ | ‘.’ IDENTIFIER
parameter_list ->parameter_declaration| parameter_list ‘,’ parameter_declaration
parameter_declaration ->type_definition declarator| type_definition abstract_declarator| type_definition
identifier_list ->IDENTIFIER| **identifier_list **’,’ IDENTIFIER
abstract_declarator -> ‘(’ abstract_declarator ‘)’| ‘[’ ‘]’| ‘[’ assignment_expression ‘]’| abstract_declarator ‘[’ ‘]’| abstract_declarator ‘[’ assignment_expression ‘]’| ‘[’ ‘’ ‘]’| abstract_declarator ‘[’ '’ ‘]’| ‘(’ ‘)’| ‘(’ parameter_list ‘)’| abstract_declarator ‘(’ ‘)’| abstract_declarator ‘(’ parameter_list ‘)’
expression -> assignment_expression | expression ‘,’ assignment_expression
assignment_expression -> **logical_or_expression **| monocular_expression assignment_operator assignment_expression
assignment_operator -> ‘=’|MUL_ASSIGN|DIV_ASSIGN|MOD_ASSIGN|ADD_ASSIGN|SUB_ASSIGN|LEFT_ASSIGN|RIGHT_ASSIGN| AND_ASSIGN|XOR_ASSIGN|OR_ASSIGN
logical_or_expression -> logical_and_expression | logical_or_expression OR_OP logical_and_expression
logical_and_expression -> bitwise_or_expression | logical_and_expression AND_OP bitwise_or_expression
bitwise_or_expression -> xor_expression | bitwise_or_expression ‘|’ xor_expression
xor_expression -> **and_expression **| xor_expression ‘^’ and_expression
and_expression -> equaljudge_expression | and_expression ‘&’ equaljudge_expression
equaljudge_expression -> **compare_expression **| equaljudge_expression EQ_OP compare_expression | equaljudge_expression NE_OP compare_expression
compare_expression -> **shift_expression **| compare_expression ‘<’ shift_expression | compare_expression ‘>’ shift_expression | compare_expression LE_OP shift_expression | compare_expression GE_OP shift_expression
shift_expression -> **addsub_expression **| shift_expression LEFT_OP addsub_expression | shift_expression RIGHT_OP addsub_expression
addsub_expression -> muldiv_expression | addsub_expression ‘+’ muldiv_expression| addsub_expression ‘-’ muldiv_expression
muldiv_expression -> monocular_expression | **muldiv_expression **’*’ monocular_expression | muldiv_expression ‘/’ **monocular_expression **| muldiv_expression ‘%’ monocular_expression
monocular_expression>postfix_expression| INC_OP monocular_expression| DEC_OP monocular_expression| monocular_operator monocular_expression
monocular_operator ->’+’| ‘-’| ‘*’| ‘~’| ‘!’
postfix_expression -> basic_expression | postfix_expression ‘[’ expression ‘]’| postfix_expression ‘(’ ‘)’| postfix_expression ‘(’ argument_expression_list ‘)’| postfix_expression INC_OP| postfix_expression DEC_OP
argument_expression_list->assignment_expression|argument_expression_list’,’ assignment_expression
basic_expression ->IDENTIFIER| TRUE| FALSE| CONSTANT_INT| CONSTANT_DOUBLE| ‘(’ expression ‘)’
SyntaxTree定义:
struct SyntaxTree
{
string content;
string name;
int line;
int number;
struct SyntaxTree *child;
struct SyntaxTree *sibling;
};
content存储树的注释,即语义分析用到的变量的值等;树的结构采用 first-child next-sibling的结构
语法树每个函数功能描述:
struct SyntaxTree *create_tree(string name, int num, ...);
void free_tree(SyntaxTree *node);
void print_tree(SyntaxTree *root, FILE *fp);
void analysis_tree(struct SyntaxTree *head, int leael);
void traverse_tree(SyntaxTree *node, ofstream &outfile);
void write_to_file(SyntaxTree *root, string path);
函数 | 功能 |
---|---|
create_tree | 创建树节点 |
free_tree | 释放语法树 |
traverse_tree | 遍历语法树 |
analysis_tree | 分析打印树content |
print_tree | 打印出语法树 |
write_to_file | 将树结构写入文件 |
.y和tree.cpp调用模式
# build.sh
if [ ! -d "dist" ]; then
mkdir dist
fi
flex --outfile=dist/compiler.lex.yy.cpp compiler.l
yacc --output=dist/y.tab.cpp -d compiler.y
cp tree.cpp dist/tree.cpp
cp tree.h dist/tree.h
cd dist
echo '#include"tree.h"\n' | cat - y.tab.hpp > y.tab.hpp.temp && rm y.tab.hpp && mv y.tab.hpp.temp y.tab.hpp
g++ -std=c++11 -o compiler tree.cpp compiler.lex.yy.cpp y.tab.cpp
语法树例子和图:
例如demo.c为
int main()
{
int a[10];
int i;
for (i=1;i<5;i++)
a[i] = i + 1;
for (i=1;i<5;i++)
print(a[i]);
}
生成的语法树的图为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4Np83OoP-1645692610657)(report/img/ir_tree.png)]
语义分析是编译过程的一个逻辑阶段, 语义分析的任务是对结构上正确的源程序进行上下文有关性质的审查,进行类型审查。语义分析是审查源程序有无语义错误,为代码生成阶段收集类型信息。
语义分析的地位是编译程序最实质性的工作;第一次对源程序的语义作出解释,引起源程序质的变化。
class Block:
def __init__(self, name="", type="", num=-1):
self.func = funcNode()
self.is_func = False
self.varMap = {}
self.arrayMap = {}
self.label_bk = ""
self.can_bk = False
Block这个类的作用是描述一个作用域主要用于管理这个作用域中的变量、标签和(函数信息),包括变量信息,函数信息,数组信息。
变量名 | 作用 |
---|---|
func | 函数结构体 |
is_func | 是否是函数块 |
varMap | 变量名和变量节点映射 |
arrayMap | 数组名和数组节点映射 |
label_bk | 跳转的标记名 |
can_bk | 是否需要跳转 |
class varNode:
def __init__(self):
self.name = ""
self.type = ""
self.num = -1
self.use_addr = False
self.bool_exp = ""
varNode类的作用是储存变量信息。
变量名 | 作用 |
---|---|
name | content或者临时变量名 |
type | 数据类型 |
num | 变量数量 |
use_addr | 是否是数组, 数组类型则赋值为True |
bool_exp | 存储返回值为bool的表达式 “>= <= == !=” |
class arrayNode:
def __init__(self):
self.name = ""
self.type = ""
self.num = -1
arrayNode的作用是存放数组信息,包括数组大小、命名等。
变量名 | 作用 |
---|---|
name | 数组名 |
type | 数组类型 |
num | 数组长度 |
class funcNode:
def __init__(self):
self.name = ""
self.re_type = ""
self.para_list = []
funcNode的作用是存放函数名称,返回类型和变量参数等。
变量名 | 作用 |
---|---|
name | 函数名 |
re_type | 返回类型 |
para_list | 参数结点,存储的是varnode |
class Parser:
funcPool = None
blockStack = None
root = None
root表示从json文件读入的经过语法分析后的语法树,在此基础上构建的SyntaxTree数据结构以便于自下而上的语义分析。
blockStack表示一个符号表栈,Block这个类的作用是描述一个作用域主要用于管理这个作用域中的变量、标签和(函数信息),这个list模仿了Block的进栈出栈模式。
fucPool存储源码中的函数定义和声明。
语义分析主要任务是分析源程序的含义并做出相应的语义处理。语义处理主要包含两个部分:
静态语义检查
类型检查,控制流检查,唯一性检查,其它相关的语义检查
语义翻译
生成与硬件机器相对独立的中间语言代码
符号表栈的处理包括作用域的进栈与出栈,以及对语法树的属性翻译和属性计算以解析整个语法树的具体语义,并协助翻译中间码,具体符号表栈的分析将在中间码介绍。
MiniC编译器主要实现三类语义错误检查:
错误检查具体功能实现如下:
类型定义包含变量或函数使用前是否经过定义。
if (rnode.num < 0):
self.error(primary_exp.child.line,
"Undefined variable " + content)
...
if (funcName not in self.funcPool):
self.error(post_exp.child.child.child.line,
"Undefined function " + funcName)
类型检查包含,运算符两边的变量是否可以进行计算,对数据进行的处理是否符合数据本身特性,函数返回值是否和定义函数返回值匹配,函数输入参数与定义参数类型不匹配或数量不匹配等等。
if (funcName not in self.funcPool):
self.error(post_exp.child.child.child.line,
"Undefined function " + funcName)
...
elif op == 'MOD_ASSIGN':
if node1.type != 'int' or node2.type != 'int':
self.error(assign_exp.child.line,
"The two variables should be int.")
...
if rnode.type != funcType:
self.error(node.child.sibling.line,
"return type error! doesn't match function return type.")
类型确定样例:
(这里以函数未定义为例)
int main()
{
int a[10];
function(a);
}
类型检查样例:
(这里以函数实际返回值与定义不符为例)
int function()
{
return 1.1;
}
int main()
{
int para;
para = function();
}
控制流中每个节点代表一个基本块,例如,没有任何跳跃或跳跃目标的直线代码块;跳跃目标以一个块开始,和以一个块结束。在控制流检查过程中,如果先前没有定义跳转的代码块,则会对其进行报错:
elif node.child.name == "BREAK":
num = self.getBreakBlockNumber()
if num < 0:
self.error(node.child.line, "break statement not within a loop..")
控制流检查样例:
int main()
{
int a[10];
break;
}
c语言中对函数名的要求是唯一的,不存在两个名称相同的函数名,在编译器语义分析时需要对函数名进行是否重复定义的分析:
if self.funcPool[funcName].isdefinied:
self.error(declarator.child.child.line, "Function " +
funcName + " has been defined before.")
唯一性检查样例:
int function()
{
int a = 1;
}
int function()
{
int a = 2;
}
int main()
{
int a[10];
}
Grammer | Description |
---|---|
LABEL label_name : | 声明标签 |
left := right | 赋值语句 |
right: var binomial_op var | var | unary_op var | *temp | &temp | array_element | CALL f(var1,var2,var3…) | |
var: temp | constant | |
left: temp | array_element | *temp | |
array_item: temp[var] | |
temp必须只含有小写英文字母、数字、下划线,且首字符是字母 | |
GOTO label1 | 无条件跳转至标签处 |
IF var GOTO label1 | 条件语句 |
IFNOT var GOTO label1 | 条件语句 |
RETURN var | 函数返回 |
MALLOC var1[size] | 申请大小为size的内存空间,并将申请到的连续内存首地址赋给var1 |
CALL f (var1,var2,var3…) | 调用函数并不需要返回值 |
Function f(var1,var2,var3…) | 声明函数f |
符号表 各种操作
例子
~ bash build.sh
~ bash test.sh
~ python load_json.py
~ python load_quadruples.py
源码:
int main()
{
int a, b, result;
a = read();
b = read();
result = a + b;
print(result);
result = a - b;
print(result);
result = a * b;
print(result);
result = a / b;
print(result);
result = a % b;
print(result);
a++;
b++;
print(a);
print(b);
}
语法树:
中间码:
FUNCTION main :
temp0 := #10
temp2 := #4
temp1 := temp2 * temp0
temp2 := #1
var0 := temp2
LABEL lab :
temp3 := #5
temp4 := var0 <= temp3
IF var0 GOTO temp3
GOTO goto
LABEL lab :
temp7 := #4
temp6 := var0 * temp7
temp5 := &var0 + temp6
temp8 := #1
temp9 := var0 + temp8
temp5 := temp9
GOTO goto
LABEL lab :
temp10 := #1
var0 := temp10
LABEL lab :
temp11 := #5
temp12 := var0 <= temp11
IF var0 GOTO temp11
GOTO goto
LABEL lab :
temp15 := #4
temp14 := var0 * temp15
temp13 := &var0 + temp14
ARG temp13
CALL print
GOTO goto
LABEL lab :
int main()
{
int a, b, result;
a = read();
b = read();
if (a > b)
{
result = a - b;
print(result);
}
else if(a == b)
{
result = a - b;
print(result);
}
else
{
result = b - a;
print(result);
}
}
```http://www.biyezuopin.vip
语法树:
![](https://img-blog.csdnimg.cn/img_convert/ace1be3a69096bfdf7ec42c939695c69.png)
中间码:
FUNCTION main :
var0 := CALL read
var1 := CALL read
temp2 := var0 >= var1
IF var0 GOTO var1
GOTO goto
LABEL lab :
temp3 := var0 - var1
var2 := temp3
ARG var2
CALL print
GOTO goto
LABEL lab :
IF var0 GOTO var1
GOTO goto
LABEL lab :
temp5 := var0 - var1
var2 := temp5
ARG var2
CALL print
GOTO goto
LABEL lab :
temp6 := var1 - var0
var2 := temp6
ARG var2
CALL print
LABEL lab :
LABEL lab :
#### 6.2.3 循环语句测试
```c++
int main()
{
int a[10], i, result;
for (i=1;i<10;i++)
{
a[i] = i;
print(a[i]);
}
i = 10;
while(i>1)
{
print(a[i]);
if(a[i]<5)
{
break;
}
}
}
语法树:
中间码:
http://www.biyezuopin.vip
FUNCTION main :
temp0 := #10
temp2 := #4
temp1 := temp2 * temp0
temp2 := #1
var0 := temp2
LABEL lab :
temp3 := #10
temp4 := var0 <= temp3
IF var0 GOTO temp3
GOTO goto
LABEL lab :
temp7 := #4
temp6 := var0 * temp7
temp5 := &var0 + temp6
temp5 := var0
temp10 := #4
temp9 := var0 * temp10
temp8 := &var0 + temp9
ARG temp8
CALL print
GOTO goto
LABEL lab :
temp11 := #10
var0 := temp11
LABEL lab :
temp12 := #1
temp13 := var0 >= temp12
IF var0 GOTO temp12
GOTO goto
LABEL lab :
temp16 := #4
temp15 := var0 * temp16
temp14 := &var0 + temp15
ARG temp14
CALL print
temp19 := #4
temp18 := var0 * temp19
temp17 := &var0 + temp18
temp20 := #5
temp21 := temp17 <= temp20
IF temp17 GOTO temp20
GOTO goto
LABEL lab :
GOTO goto
LABEL lab :
GOTO goto
LABEL lab :
源码:
int fact(int i) {
if (i <= 1) {
return 1;
}
else{
return i * fact(i - 1);
}
}
int main() {
int i;
int times;
times=read();
for(i = 1; i < times + 1; i++)
print( fact(i) );
return 0;
}
语法树:
中间码:
FUNCTION fact :
PARAM var0
temp0 := #1
temp1 := var0 <= temp0
IF var0 GOTO temp0
GOTO goto
LABEL lab :
temp2 := #1
RETURN temp2
GOTO goto
LABEL lab :
temp3 := #1
temp4 := var0 - temp3
ARG temp4
temp5 := CALL fact
temp6 := var0 * temp5
RETURN temp6
LABEL lab :
FUNCTION main :
var2 := CALL read
temp8 := #1
var1 := temp8
LABEL lab :
temp9 := #1
temp10 := var2 + temp9
temp11 := var1 <= temp10
IF var1 GOTO temp10
GOTO goto
LABEL lab :
ARG var1
temp12 := CALL fact
ARG temp12
CALL print
GOTO goto
LABEL lab :
困难
解决方案
困难
解决方案
困难
解决方案
困难
解决方案
MiniC基本实现了C语言的部分常用功能,能够进行正确的词法、语法、语义分析并生成语法树进行可视化以及中间码。
未实现的功能还有很多,例如:
在实验中,对词法和语法分析有了更深刻的了解,能熟练运用lex和yacc进行操作,同时,对语义分析和错误纠正也有一定的了解。结合课程所学知识,能够通过语法树转化成四元码并生成中间码。在未来,我们将会对MiniC编译器进行优化与改善以实现更多功能。