sfti 编译器简介

  • 使用递归下降子程序法
  • 处理c0文法
  • 生成pcode
  • 其他若干特征

运行方法

编译器 指令 make 生成sfti.exe sfti.exe 1.c 生成对应的pcode

模拟器 指令 make simulator 处理对应的pcode simulator.exe pcode

以上是windos下的指令,liunx下自行更改CC变量

编译器架构

主要分成这三部分,采用中间树表示是因为这样可以简化方便优化,并且原则上需要有一个中间表示 因为有中间表示可以方便生成针对其他原型机的代码(不只是pl0机) 分别实现虽然看起来有一些冗余,但是实际上是模块化的一种**,便于后期的分割。 编译器构建中使用的模块将在最后一一分析。 如果有人要看源码的话

  • 词法分析部分
  • 中间树表示生成
  • pcode生成

词法分析部分

词法分析部分在 词法分析程序实验报告 已经提过,不再赘述

中间树表示

采用自己的中间树表示,并且在中间树表示这里处理所有的错误,如果有语法等的错误,将在这一层级报错。

错误处理

见后表

符号表

参考了现代编译器原理一书的实现,使用栈式哈希链表(我自己起的)的数据结构完成,O1的访问时间内完成变量的存取。 关于符号表,实际上在中间树构建和生成Pcode中分贝使用了env 模块和 penv 模块作为对应的符号表条目,这样可以使得模块的功能专门化

pcode生成

其他语法特性

静态类型检查

对各个语句等的表达式类型进行检查,在构建中间结果的时候就会输出错误

支持string 类型

支持输出,函数参数

支持float 类

支持调用,赋值,运算等

隐式类型转化

使用float和int 混用的表达式将被认为是float 类型(1+2.0) 隐式类型转换还体现在其他一些地方,这里不叙述了。

关于pcode 的若干问题

使用教材 pxx 的pcode 并不能完全表示c0语法的特征,所以我针对指令集进行了一点微小修改 并且对运行模型进行了一定程度的修改,下面就我使用的指令给与说明。 b 栈基址 t 栈顶指针1 S 栈区 ip 当前指令指针

  1. LOD x,y
    将层次差x,offset 为y的变量载入栈顶 t=t+1
  2. STO x,y
    将栈顶的值存入层次差x,offset为y 的变量中 t=t-1
  3. MKS x,y
    栈标记指令, y是新栈帧中分配给参数的空间量 完成的动作如下 S(t+1)=b //动态连设置 S(t+2)=b //静态链的暂时设置 b=t t=t+4+y //分配相应的参数和栈帧空间
  4. CAL x,y 调用指令,层次差x,起始地址y S(b+3)=ip+1 S(b+2)=SL SL 的获取方式是不断沿着静态链上x次 寻直到找到调用的函数 ip=y
  5. INT x,y
    将栈顶增加y t=t+y
  6. JMP x,y
    无条件跳转 ip=y
  7. JPC x,y
    有条件跳转 t=t-1 if S(t-1) == 0 ip = y
  8. RED x,y
    等待输入,并将输入填入层次差为x offset 为y 的变量
  9. WRT x,y
    输出栈顶数字 t=t-1
  10. WRTS x,y
    输出栈顶的字符 t=t-1
  11. WRTF x,y 输出栈顶的浮点数
  12. FLT x,y 浮点数转换 if y==1 将栈顶浮点转换为整形 if y==0 将栈顶整形转换为浮点
  13. OPRF x,y 同下,操作数为浮点数
  14. OPR x,y
    根据y执行不同的运算操作,如下
	 case y==0 :
	 	执行返回指令,将栈顶元素作为返回值返回
	 	S(b)=S(t-1)
	 	t=b+1
	 	ip=S(b+3)
	 	b=S(b+1)
	 case y==2 :
	 	执行add 操作 
	 	S(t-2)=S(t-2)+S(t-1)
	 	t=t-1
	 case y==3 :
	 	执行minus操作
	 	S(t-2)=S(t-2)-S(t-1)
	 	t=t-1
	 case y==4 :
	 	执行乘法操作
	 	S(t-2)=S(t-2)*S(t-1)
	 	t=t-1
	 case y==5 :
	 	执行除法操作
	 	S(t-2)=S(t-2)/S(t-1)
	 	t=t-1
	 case y==8 :
	 	等于 同下
	 case y==9 :
	 	不等于
	 	if S(t-2)==S(t-1)
	 		S(t-2)=0
	 	else 
	 		S(t-2)=1
	 case y==10: 
	  	小于
	  	ifS(t-2) < S(t-1)
	  	 	S(t-2)=1
	  	 else 
	  	 	S(t-1)=0
	 case y=11 
	 	大于 同上
	 case y=12
	 	大于等于
	 case y=13
	 	小于等于

修改后的运行时模型

因为pl0树上的pcode是不够的,并且模型也不够表示,所以这里对活动记录,栈帧表示进行了些许修改。

修改后的活动记录,每一个栈帧自下而上分别是 返回值,动态链,静态链, 返回地址 AR 因为要进行函数参数传递的缘故,参考后面的pascal-s 添加了mks 指令,用在cal指令前设置一个栈帧,其中就包括了参数的分配,具体的步骤见上

错误对照表

code

  1. 函数参数传递类型不符合 定义处
  2. 函数参数传递类型不符合 使用处
  3. 函数参数过少
  4. 函数参数过多
  5. 这里期望有一个;
  6. 这里应该是一个int
  7. 这里应该是一个string
  8. 这里因该是一个float
  9. 这里应该是一个赋值=
  10. 变量在定义时不可以初始化
  11. 不明类型(只允许int string float)
  12. 变量定义时的不明符号
  13. 不合法的返回类型
  14. 不合法的主函数返回类型
  15. 这里因该是(
  16. 这里应该是)
  17. 函数参数中的未知类型
  18. 这里应该是, 或者)
  19. 函数}结束符丢失
  20. 函数间非法的分隔符
  21. 语句序列{丢失
  22. 未知语句类型
  23. 语句(丢失
  24. 语句 )丢失
  25. 未知的变量被引用
  26. 未知的因子类型
  27. 函数返回类型出错
  28. 未知变量被调用(只有函数可以被调用)
  29. 不是一个合理的左值
  30. 赋值语句的类型不一致

注: 在出现比较微小的错误后程序仍然可以正常编译,但是此时不保证程序的正确性。 此时程序不可被解释执行(需要先删除错误信息)

模块功能表

absyn

负责中间树生成,表示程序中的各个语义结构

c0error

负责错误处理,将程序结构组织成链表

c0lex

词法处理程序

env

中间树生成阶段的条目

penv

pcode生成中的条目

lev

pcode生成中的栈帧管理极简

semant

语法分析生成中间树的主要代码

symbol

符号表的数据结构

table

哈希表

topcode

中间树生成pcode的主要代码

util

内存分配等常用函数

simulator

pl0 模拟器魔改

测试以及运行

提供了 1.c -5.c 是正确的样例程序

6.c 62.c 分别是int 和 float 版本的阶乘程序

7.c -12.c 是错误程序,错误信息提供在错误信息处理.docx中,以供查看

GUI

没有GUI 编译器要什么GUI

感想

感谢《现代编译原理(C语言描述)》 ,部分数据结构和**借鉴此书。 代码量接近4k行,也耗费了不少时间。因为《现代编译原理(C语言描述)》中的编译器叫tiger 表示纪念,这个叫做sfti。