编译器设计实验

转载自:我的个人博客
一个 Java 实现的 TXT 语言编译器, 目标平台为 RISC-V 32 (指令集 RV32M)。编译器大致分为词法分析、语法分析、语义分析及中间代码生成、目标代码生成四个部分。

代码:A Simple Compiler

源语言的示例代码

int result;
int a;
int b;
int c;
a = 8;
b = 5;
c = 3 - a;
result = a * b - ( 3 + b ) * ( c - a );
return result;

词法分析

编码表

类别正则表达式
returnreturn
==
,,
Semicolon;
++
--
**
//
((
))
id[a-zA-Z_][a-zA-Z]*
IntConst[0-9]+

正则文法

G = ( V , T , P , S ) G=(V,T,P,S) G=(V,T,P,S),其中 V = { S , A , B , C , 0 , 1 , 2 , … , 9 , c h a r } V=\{S, A, B, C, 0,1,2,…,9, char\} V={S,A,B,C,0,1,2,,9,char}, T = { 任意符号 } T=\{任意符号\} T={任意符号} P P P定义如下

约定: [ s − t ] [s-t] [st]表示从s到t的所有ASCII字符中的一个

标识符: S → [ a − z ] A , S \rightarrow [a-z] A, S[az]A, A → [ a − z ] A ∣ [ 0 − 9 ] A ∣ ϵ A \rightarrow [a-z] A | [0-9] A | ϵ A[az]A[09]Aϵ

整常数: S → [ 1 − 9 ] B , S \rightarrow [1-9] B, S[19]B, B → [ 0 − 9 ] B ∣ ϵ B \rightarrow [0-9] B | ϵ B[09]Bϵ

运算符: S → C , S \rightarrow C, SC, C → = ∣ ∗ ∣ + ∣ − ∣ / C \rightarrow = | * | + | - | / C→=+∣/

分隔符: S → D , S \rightarrow D, SD, D → ; ∣ ( ∣ ) ∣ , D \rightarrow ; | ( | ) | , D;(),

状态转换图

根据识别的编码不同,将状态机划分为不同的状态,共计14个状态。在每识别到终止状态时,调用相关的函数,将语法单元添加到维护的表格中,并重新回到起始状态。状态机DFA对应的状态转换图如下图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SZUEAaHS-1675226405910)(/img/A-Simple-Compiler/状态转换图.png)]

流程

使用列表存储每个识别出的语法单元,读入输入代码,从前往后逐个读取字符,若遇到空格等其他空白字符跳过,否则,按照状态转换图识别语法单元,将其加入token列表中。若此语法单元为单词,且不是标识符,且不在符号表中,则其为新识别出的变量,将其添加到符号表中。

语法分析

状态栈和符号栈的数据结构

使用Stack类作为状态栈和符号栈的数据结构,状态栈的元素的基类为Status类,符号栈的基类为Token类(后续发现实际分析过程中不需要用到符号栈)。通过peek()函数获取栈顶元素,通过push()函数向栈中压入元素,通过pop()函数弹出栈顶元素,通过empty()函数判断栈是否为空。

流程

  1. 向状态栈中压入初始状态,向符号栈中压入符号“$”。
  2. 依次遍历输入串中的字符,根据当前状态栈栈顶的状态,判断此时应执行的Action。
  3. 若执行的动作为移进,则根据LR1分析表获取要转移到的状态(程序中存储在action对应的Status对象中),将状态压入状态栈,将字符压入符号栈,并调用移入时对应的观察者的函数(callWhenInShift函数),通知观察者当前读到的字符和要转移到的状态。
  4. 若执行的动作为规约,则根据LR1分析表获取要规约的产生式(程序中存储在action对象的production属性中),调用规约时对应的观察者的函数(callWhenInReduce函数),通知观察者当前读到的字符和要用于规约的产生式,ProductionCollector观察者会将所有规约的产生式依次存储在列表中;获取产生式的头和体,将产生式的体对应的符号数从状态栈和符号栈中弹出,并将产生式的头压入符号栈。根据当前状态和状态站顶部状态查找LR1表,判断此时需要压入状态栈顶部的状态,并将其压入。
  5. 若执行的动作为接受,则调用处于接受状态时对应的观察者的函数(callWhenInAccept)通知各个观察者,语法分析结束。
  6. 重复执行2-5步,直到读完代码中的所有字符,或状态栈为空,或产生错误。

语义分析和中间代码生成

翻译方案

S → D   i d ; { i d . t y p e = D . t y p e } S \rightarrow D \ id; \{ id.type = D.type \} SD id;{id.type=D.type}

D → i n t ; { i n t . t y p e = I N T , D . t y p e = i n t . t y p e } D \rightarrow int ; \{ int.type = INT, D.type = int.type \} Dint;{int.type=INT,D.type=int.type}

其余产生式规约式不执行任何动作

使用的数据结构

**语义分析:**使用基类位SourceCodeType的栈(Stack)作为类型栈typeStack的数据结构,存储当前符号栈内对应符号的类型(type);使用基类为Token的栈(Stack)作为符号栈tokenStack的数据结构,存储移进的单词(token)

**中间代码生成:**使用基类为Instruction的列表(List)instructions作为中间代码存储的数据结构,依次存储生成的中间代码,使用基类为IRValue的栈(Stack)作为符号栈irValueStack的数据结构,存储符号栈对应的ir表达式的值。

流程

  1. 当动作为接受时,不执行任何操作。
  2. 当动作为移进时,将对应的符号压入符号栈。如果此符号为关键字int,则将INT压入类型栈,否则将null压入类型栈。
  3. 当动作为规约时,首先判断要执行规约的产生式编号,若编号为4,则跳到第4步,若编号为5,则跳到第5步,否则,调到第6步.
  4. 此时待规约的产生式为 S → D   i d ; S \rightarrow D \ id; SD id;。分别弹出并获取符号栈和类型栈顶端的两个元素,并设置为符号栈第一个元素(id)在符号表中的类型为类型栈从上往下第二个元素的类型(D对应的类型)。并向符号栈和类型栈中压入null(对应左部的S)。
  5. 此时待规约的产生式为 D →   i n t ; D \rightarrow \ int ; D int; 弹出符号栈和类型栈栈顶的元素,将类型栈栈顶元素的类型作为左部符号的类型,再压入符号栈,向符号栈中压入null(对应左部的D)。
  6. 此时待规约的产生式没有特殊的翻译动作,只需从符号栈和类型栈中弹出与产生式右部元素个数一致的元素,并压入null作为左部对应的符号/类型。

目标代码生成

对中间代码进行预处理

  1. 将操作两个立即数的 BinaryOp 直接进行求值得到结果, 然后替换成 MOV 指令。
  2. 将操作一个立即数的指令 (除了乘法和左立即数减法) 进行调整, 使之满足 a := b op imm 的格式。
  3. 将操作一个立即数的乘法和左立即数减法调整, 前插一条 MOV a, imm, 用 a 替换原立即数, 将指令调整为无立即数指令.
  4. 舍弃Ret指令之后的所有指令

识别各变量最后一次使用的位置

从后往前遍历预处理后的中间代码序列。用列表存储当前已经遍历到过的变量。每处理一条指令,分别判断它的两个原操作数中是否为立即数,若不是,再查看此变量是否被遍历到过。若此变量从未被遍历到过,说明此变量最后一次出现的位置便为这条指令,将此信息存入列表中保存。

在遍历过程中,若变量出现在指令的目标操作数位置,且不出现在源操作数中,说明此指令前此变量的值与后续执行过程无关,将此变量从存储但钱已经遍历到过的变量列表中删去。

将中间代码转化为目标代码

创建AsmInstruction类,表示riscv形式的汇编代码类,创建AsmInstructionKind枚举类,表示riscv代码的各种类型。

创建RegisterAssigner类,用于分配和回收空闲的寄存器。其中,通过列表维护空闲寄存器,利用双射Map维护变量和已利用的寄存器之间的双射关系。当请求分配寄存器时,从空闲寄存器列表随机分配一个寄存器,并将其添加到双射Map中。当回收某一寄存器时,将其从双射Map中删除,并将相应寄存器重新存放回空闲寄存器列表。
遍历处理后的中间代码,提取每条指令的左右操作数,将其转化为相应的AsmInstruction对象,并存放到列表中。

根据维护的变量最后一次出现的位置信息,若当前指令为变量最后一次出现的指令,则将相应变量对应的寄存器释放。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值