/compiler

Primary LanguageC++

  1. 使用方法
  2. 设计思路与架构
  3. 问题与解决方案
  4. 分工

1. 使用方法

1.1 脚本使用方法

进入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

1.2 编译使用方法

使用如下命令编译生成编译器

./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?# 《测试用例》

2. 设计思路与项目架构

/tree目录下,是相关的抽象语法树节点的实现

2.1 抽象语法树

抽象语法树节点,分为语句节点StmtNode、表达式节点ExprNode、标识符节点IDNode、类型节点TypeNode和临时节点TempNode

不同的节点,由不同类型不同数量的子节点构成。

2.1.1 临时节点TempNode

其中临时节点TempNode唯一的作用,就是存储没有明确语义的节点以及记录一定的信息,以生成其他类型的节点

2.1.2 语句节点StmtNode

语句节点StmtNode是各种语句的节点, 包括表达式语句节点ExprStmtNode、if语句节点IFStmt、变量声明节点VarDefStmt、for语句节点ForStmt、while语句节点WhileStmt、语句块节点BlockStmt、函数声明节点FuncDecStmt、函数定义节点FuncDefStmt、结构体声明节点StructDecStmt、结构体定义节点StructDefStmt、返回语句节点ReturnStmt

2.1.3 表达式语句ExprNode

表达式语句ExprNode是各种表达式的节点, 包括一元运算表达式OP1ExprNode、二元运算表达式OP2ExprNode、结构体取成员表达式MemberExprNode、函数调用表达式FunCallExprNode、变量表达式VarExprNode和常量表达式ConstExprNode

表达式有各种属性,类型,是否为左值(可以取址),是否为常量

2.1.4 标识符节点IDNode

标识符节点IDNode,是存储标识符的节点,可以向符号表查找或注册一个标识符。可以获取标识符的类型,如变量名,函数名,结构体名。

2.1.5 类型节点TypeNode

类型节点TypeNode,是存储类型的节点。 类型有基本类型、函数类型、数组类型和指针类型。

其中函数类型、数组类型和指针类型是复合类型。

属性:需要的存储空间,函数类型和结构体类型还有标识符节点的属性。

并分别实现如下方法,是否可以用另一个类型的变量赋值,是否与另一个类型相同,是否可以当成bool类型,是否可以进行加减运算等

2.2 符号表

/symbol文件夹下

符号表SymbolTable,用于记录一个作用域内的标识符。

符号表有父符号表。

为了生成子符号表的时候知道哪些符号属于子符号表,记录了每一个节点的第一个词法单元的序号,所有在某个序号之后的标识符节点属于子作用域,其他标识符属于父作用域。

2.3 目标代码生成

抽象语法树都有output方法,用于输出目标代码

其中表达式节点output调用calValue方法,并得到ValPtr类型的结构,此数据结果记录了表达式的值或地址的运算数

ValPtr

/register文件夹下

ValPtrValue类型指针的包装类,有一个Value类型指针的属性

Value

Value是用于记录不同类型的运算数的数据结构

Value有不同的类型,包括寄存器RegValue,内存MemValue,常量ConstValue,状态StateValue

寄存器RegValue记录了一个寄存器名,如eax,ebx

状态StateValue是在进行了比较之后,可以进行条件跳转的状态,记录了比较结果为真/假时的跳转指令如jgje

内存MemValue记录了内存的地址,是一个ValPtr类型的属性

常量ConstValue记录了一个字符串和一个偏移,如ebp-0x81

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来比较,为真/假时对应的跳转语句分别是jneje

寄存器管理

每个寄存器会记录当前被哪一个ValPtr所占用

当寄存器不足时,选择一个ValPtr进行store操作,暂时存到栈中

实现holdReg,禁止对一个ValPtr进行store操作,表示,此寄存器处于不允许暂存到栈中的状态 使用unholdReg解除这种状态

3. 问题与解决方案

1. 变量声明语句中,声明的变量的作用域无从判断。

解决办法:记录每一个抽象语法树节点的第一个词素的序号。由此,对于一个可以产生作用域的语句,可以区分一个变量声明语句在它之前还是之后。

2. 同一个作用域的标识符要指向同一个

解决办法:在变量声明,结构体声明,函数声明时,注册标识符,其他时候向符号表查找标识符。

3. 函数递归调用时,找不到自己的函数名的标识符。

解决办法:把函数定义语句,拆分成函数声明语句+定义部分。于是,函数定义内部的语句归约时,已经有了当前函数名的标识符。

4. 在结构体中,成员变量不可在定义时初始化。

解决办法:将定义语句细分为声明语句(不可赋值)和定义语句(可赋值)。

5. 结构体定义内声明自己类型指针,找不到类型名的标识符。

解决办法:同3。

6. 在识别多维指针和多维数组时,产生归约冲突。

解决方法:单独识别指针,将其作为一个前缀修饰符给变量作修饰。

7. for语句的情况较多,用一个产生式体识别会给语义动作的添加带来麻烦。

解决方法:分别用不同的产生式体识别for语句的每一种可能情况。

8. 变量定义语句中,指针、数组、初始化等信息不能很好地区分

解决办法:在节点上加上message,用于区分这些信息。

9. 很多情况下,寄存器内的值不允许临时存到内存上。

解决办法:使用类似加锁的操作,保护寄存器,即使寄存器不足时,也只能腾出其他寄存器的空间。

10. 一个记录了地址的寄存器,此寄存器内的值可能被暂存到了内存上,于是,使用时,就会产生类似"[[ebp-0x10]]"的操作数。

解决办法:对这样记录了地址的寄存器进行加锁,禁止其暂存到内存上

11. 对于数组和结构体这样的无法直接存入寄存器内的类型,获取其值时的行为不知道如何定义。

解决办法:获取其值时也是获取其地址,但是标记这是表示值。结构体类型变量赋值时,对变量所有内存依次进行赋值。

4. 项目分工

按照姓名排序

  • 黄子维1813636:语法分析 词法分析
  • 熊潇1813036: 语法分析 汇编程序
  • 阳铠行1813039:词法分析 类型检查
  • 张子博1813664:错误分析 文档编写
  • 张智强1811237:语法分析 符号表 类型检查 汇编程序 debug