- 使用方法
- 设计思路与架构
- 问题与解决方案
- 分工
进入code
文件夹
cd code
如果下面的操作不能报错找不到文件
确保这四个文件./make.sh
、./build/make.sh
、./lexer-grammar.sh
、./compile-run.sh
的换行符是LF
而不是CRLF
,可以在vscode中打开,查看右下角
使用如下命令,赋予脚本文件执行权限
sudo chmod -x ./make.sh
sudo chmod -x ./lexer-grammar.sh
sudo chmod -x ./compile-run.sh
使用时,按如下方法输入命令即可
./make.sh
./lexer-grammar.sh
./compile-run.sh
如果未能赋予执行权限,则需要按如下方法使用
/bin/bash ./make.sh
/bin/bash ./lexer-grammar.sh
/bin/bash ./compile-run.sh
使用如下命令编译生成编译器
./make.sh
生成的编译器为./build/compiler
运行编译器
./build/compiler
将编译./test.c
,并得到词法分析,语法分析和汇编代码,分别输出到./output/lexer.output
./output/grammar.output
,./output/asm.output
中,可以使用下面的命令直接查看结果。
可以使用如下命令,直接编译./test.c
并查看./output/lexer.output
的词法分析输出和./output/grammar.output
的语法分析树输出
./lexer-grammar.sh
使用如下命令,编译./test.c
,查看./output/asm.output
中的汇编代码,得到可执行文件并执行
./compile-run.sh
可以使用链接中测试用例,复制到.test.c
中进行测试
https://www.yuque.com/docs/share/99d1a9cf-138b-49e5-8852-62a19628c4c0?# 《测试用例》
在/tree
目录下,是相关的抽象语法树节点的实现
抽象语法树节点,分为语句节点StmtNode
、表达式节点ExprNode
、标识符节点IDNode
、类型节点TypeNode
和临时节点TempNode
不同的节点,由不同类型不同数量的子节点构成。
其中临时节点TempNode
唯一的作用,就是存储没有明确语义的节点以及记录一定的信息,以生成其他类型的节点
语句节点StmtNode
是各种语句的节点,
包括表达式语句节点ExprStmtNode
、if语句节点IFStmt
、变量声明节点VarDefStmt
、for语句节点ForStmt
、while语句节点WhileStmt
、语句块节点BlockStmt
、函数声明节点FuncDecStmt
、函数定义节点FuncDefStmt
、结构体声明节点StructDecStmt
、结构体定义节点StructDefStmt
、返回语句节点ReturnStmt
表达式语句ExprNode
是各种表达式的节点,
包括一元运算表达式OP1ExprNode
、二元运算表达式OP2ExprNode
、结构体取成员表达式MemberExprNode
、函数调用表达式FunCallExprNode
、变量表达式VarExprNode
和常量表达式ConstExprNode
表达式有各种属性,类型,是否为左值(可以取址),是否为常量
标识符节点IDNode
,是存储标识符的节点,可以向符号表查找或注册一个标识符。可以获取标识符的类型,如变量名,函数名,结构体名。
类型节点TypeNode
,是存储类型的节点。
类型有基本类型、函数类型、数组类型和指针类型。
其中函数类型、数组类型和指针类型是复合类型。
属性:需要的存储空间,函数类型和结构体类型还有标识符节点的属性。
并分别实现如下方法,是否可以用另一个类型的变量赋值,是否与另一个类型相同,是否可以当成bool类型,是否可以进行加减运算等
在/symbol
文件夹下
符号表SymbolTable
,用于记录一个作用域内的标识符。
符号表有父符号表。
为了生成子符号表的时候知道哪些符号属于子符号表,记录了每一个节点的第一个词法单元的序号,所有在某个序号之后的标识符节点属于子作用域,其他标识符属于父作用域。
抽象语法树都有output
方法,用于输出目标代码
其中表达式节点output
调用calValue
方法,并得到ValPtr
类型的结构,此数据结果记录了表达式的值或地址的运算数
在/register
文件夹下
ValPtr
是Value
类型指针的包装类,有一个Value
类型指针的属性
Value
是用于记录不同类型的运算数的数据结构
Value
有不同的类型,包括寄存器RegValue
,内存MemValue
,常量ConstValue
,状态StateValue
寄存器RegValue
记录了一个寄存器名,如eax
,ebx
等
状态StateValue
是在进行了比较之后,可以进行条件跳转的状态,记录了比较结果为真/假时的跳转指令如jg
,je
等
内存MemValue
记录了内存的地址,是一个ValPtr
类型的属性
常量ConstValue
记录了一个字符串和一个偏移,如ebp-0x8
,1
Value
有一些基本方法
str
方法
得到可以输出到目标代码的字符串如
寄存器RegValue
输出诸如eax
,ebx
等字符串
常量ConstValue
输出诸如1
,ebp-0x8
等字符串
状态StateValue
无法输出
内存MemValue
输出[...]
其中...
是其他运算数的str
输出
load
方法
load
方法,是将运算数加载到寄存器,转化成寄存器运算数
一般是由ValPtr
调用,将ValPtr
存储的运算数的指针,替换成一个寄存器RegValue
类型的运算数指针
store
方法
同理,store
方法是把一个运算数存到栈中,替换成转化成一个内存运算数的指针
GoTo
方法
GoTo
方法 是ValPtr
的方法,输出一个跳转指令。
主要是针对,状态StateValue
,状态StateValue
记录了一次比较之后,判断结果为真/假时的跳转指令。如进行大于判断,则状态StateValue
记录了大于跳转jg
和小于等于跳转jle
同时寄存器RegValue
也有对应的接口。
先与0
来比较,为真/假时对应的跳转语句分别是jne
和je
每个寄存器会记录当前被哪一个ValPtr
所占用
当寄存器不足时,选择一个ValPtr
进行store
操作,暂时存到栈中
实现holdReg
,禁止对一个ValPtr
进行store
操作,表示,此寄存器处于不允许暂存到栈中的状态
使用unholdReg
解除这种状态
解决办法:记录每一个抽象语法树节点的第一个词素的序号。由此,对于一个可以产生作用域的语句,可以区分一个变量声明语句在它之前还是之后。
解决办法:在变量声明,结构体声明,函数声明时,注册标识符,其他时候向符号表查找标识符。
解决办法:把函数定义语句,拆分成函数声明语句+定义部分。于是,函数定义内部的语句归约时,已经有了当前函数名的标识符。
解决办法:将定义语句细分为声明语句(不可赋值)和定义语句(可赋值)。
解决办法:同3。
解决方法:单独识别指针,将其作为一个前缀修饰符给变量作修饰。
解决方法:分别用不同的产生式体识别for语句的每一种可能情况。
解决办法:在节点上加上message,用于区分这些信息。
解决办法:使用类似加锁的操作,保护寄存器,即使寄存器不足时,也只能腾出其他寄存器的空间。
解决办法:对这样记录了地址的寄存器进行加锁,禁止其暂存到内存上
解决办法:获取其值时也是获取其地址,但是标记这是表示值。结构体类型变量赋值时,对变量所有内存依次进行赋值。
按照姓名排序
- 黄子维1813636:语法分析 词法分析
- 熊潇1813036: 语法分析 汇编程序
- 阳铠行1813039:词法分析 类型检查
- 张子博1813664:错误分析 文档编写
- 张智强1811237:语法分析 符号表 类型检查 汇编程序 debug