1.文法说明
<加法运算符> ::= +|-
<乘法运算符> ::= *|/
<关系运算符> ::= <|<=|>|>=|!=|==
<字母> ::= _|a|...|z|A|...|Z
<数字> ::= 0|<非零数字>
<非零数字> ::= 1|...|9
<字符> ::= '<加法运算符>'|'<乘法运算符>'|'<字母>'|'<数字>'
<字符串> ::= "{十进制编码为32,33,35-126的ASCII字符}"
<程序> ::= [<常量说明>][<变量说明>]{<有返回值函数定义>|<无返回值函数定义>}<主函数>
<常量说明> ::= const<常量定义>;{ const<常量定义>;}
<常量定义> ::= int<标识符>=<整数>{,<标识符>=<整数>}
| char<标识符>=<字符>{,<标识符>=<字符>}
<无符号整数> ::= <非零数字>{<数字>}
<整数> ::= [+|-]<无符号整数>|0
<标识符> ::= <字母>{<字母>|<数字>}
<声明头部> ::= int<标识符>|char<标识符>
<变量说明> ::= <变量定义>;{<变量定义>;}
<变量定义> ::= <类型标识符>(<标识符>|<标识符>‘[’<无符号整数>‘]’){,(<标识符>|<标识符>‘[’<无符号整数>‘]’) }
<类型标识符> ::= int | char
<有返回值函数定义> ::= <声明头部>‘(’<参数表>‘)’ ‘{’<复合语句>‘}’
<无返回值函数定义> ::= void<标识符>‘(’<参数表>‘)’‘{’<复合语句>‘}’
<复合语句> ::= [<常量说明>][<变量说明>]<语句列>
<参数表> ::= <类型标识符><标识符>{,<类型标识符><标识符>}|<空>
<主函数> ::= void main‘(’‘)’ ‘{’<复合语句>‘}’
<表达式> ::= [+|-]<项>{<加法运算符><项>}
<项> ::= <因子>{<乘法运算符><因子>}
<因子> ::= <标识符>|<标识符>‘[’<表达式>‘]’|<整数>|<字符>|<有返回值函数调用语句>|‘(’<表达式>‘)’
<语句> ::= <条件语句>|<循环语句>|<情况语句>|‘{’<语句列>‘}’|<有返回值函数调用语句>;
|<无返回值函数调用语句>;|<赋值语句>;|<读语句>;|<写语句>;|<空>;|<返回语句>;
<赋值语句> ::= <标识符>=<表达式>|<标识符>‘[’<表达式>‘]’=<表达式>
<条件语句> ::= if ‘(’<条件>‘)’<语句>
<条件> ::= <表达式><关系运算符><表达式>|<表达式> //表达式为0条件为假,否则为真
<循环语句> ::= do<语句>while ‘(’<条件>‘)’
<常量> ::= <整数>|<字符>
<情况语句> ::= switch ‘(’<表达式>‘)’ ‘{’<情况表> ‘}’
<情况表> ::= <情况子语句>{<情况子语句>}
<情况子语句> ::= case<常量>:<语句>
<有返回值函数调用语句> ::= <标识符>‘(’<值参数表>‘)’
<无返回值函数调用语句> ::= <标识符>‘(’<值参数表>‘)’
<值参数表> ::= <表达式>{,<表达式>}|<空>
<语句列> ::={<语句>}
<读语句> ::= scanf ‘(’<标识符>{,<标识符>}‘)’
<写语句> ::= printf‘(’<字符串>,<表达式>‘)’|printf ‘(’<字符串>‘)’|printf ‘(’<表达式>‘)’
<返回语句> ::= return[‘(’<表达式>‘)’]
附加说明:
(1)char类型的表达式,用字符的ASCII码对应的整数参加运算,在写语句中输出字符
(2)标识符不区分大小写字母
(3)写语句中的字符串原样输出
(4)数组的下标从0开始
(5)情况语句中,switch后面的表达式和case后面的常量只允许出现int和char类型;每个情况子语句执行完毕后,不继续执行后面的情况子语句
2.程序结构
本程序共有8部分组成:
主程序 main.c
词法分析程序 wordtest.c
语法&语义分析程序 grammar.c
中间代码生成程序 mid_code.c
中间代码到mips汇编语言的转化程序 final_result.c
错误处理程序 error.c
代码优化程序 optimizer.c
寄存器分配后的mips汇编转化程序 final_result2.c
3.符号表设计
由于C0文法和Pascals文法上的特点相差较多,因此在符号表上的设计和Pascals的符号表不一样。以下为符号表管理所需要的所有数据结构:
1.符号表中的一行
struct symbol{
char name[MAXNAME]; //识别符的名字,最长截取前MAXNAME个字符
int kind; /*
用来判别这个识别符的种类,设计为:
0:常量(const系列)
1:变量
2:函数(包括有返回值和无返回值)
3:函数参数
*/
int type; /*
0:void(对于无返回值函数)
1:int
2:char
*/
int value; /*
如果是一个常量,这里存放着常量的值(int)或ascii码(char);
其他类型的数据,此处填0
*/
int size; /*
如果是函数,size表示参数的个数;对于数组,size表示数组元素个数;
其他的类型,此处填写-1
*/
int in_address; /*
记录该标识符相对于所在AR首地址的偏移量(数组记录的是首地址)
*/
};
2.符号表
struct table{
symbol List[MAXLIST]; //一个symbol 数组,大小为MAXLIST
int list_index; //当前符号表的索引值(栈顶指针)int num_of_func; //当前已经声明了的函数个数intindex_of_func[MAXFUNC]; /*初始化的时候都是-1。存放着对应顺序的函数的table索引值*/
};
注解:由于C0文法是不允许嵌套定义函数的(这个和Pascals有明显区别),因此符号表中不需要level的信息,一个函数能使用的所有标识符只能是全局常量、全局变量、参数、内部的局部常量和变量(其中函数内部声明的是局部常量和变量,第一个函数声明之前声明的标识符就是全局量)。
4.存储分配方案
采用静态存储的方式存储所有需要打印的字符串和全局变量,采用动态存储分配来分配局部变量和临时变量。
对应到mips汇编中,即将所有需要打印的字符串和全局变量存储到堆空间中,用.data进行在mips汇编的开始部分事先声明。对于局部变量和临时变量,就放在运行栈中(也就是存放在对应的函数的活动记录中)
运行栈是一个动态的结构,存放的是活动记录,一个函数被调用时,就对对应的创造新的活动记录给它(活动记录中存放着显式参数、隐式参数、局部数据等内容)。将$fp更新成原有的活动记录的顶部,$sp永远指向当前栈顶的下一个空间。
再具体一点,可以这么设计:
$fp存放原有活动记录的顶部
$fp+4 存放当前函数的返回地址
$fp+8 从这里开始依次存放函数的值参、内部的局部变量、局部常量、临时变量
5.四元式设计
对应设计以下结构体:
struct mid_code{
int op;
char src1[100];
char src2[100];
char result[100];
int is_effective;
};
在本结构体中:
op: 操作符
src1: 操作数1的标识符(也可能是中间的临时变量)
sec2: 操作数2的标识符(也可能是中间的临时变量)
result: 结果的标识符(也可能是中间的临时变量)
is_effective: 这句中间代码是否有效(在代码优化后会把有些语句置为无效,不输出对应的汇编)
所有四元式具体设计如下:
编号 四元式 作用 1 CONST INT src1 a const int src1 = a;
整型常量src1的声明(a为一个整数)2 CONST CHAR src1 a const char src1 = a;
字符型常量src1的声明(a为字符)3 INT src1 int src1;
整型变量声明4 CHAR src1 char src1;
字符型变量声明5 ARRAY INT src1 NUM int src1[NUM];
整型数组声明(长度为NUM)6 ARRAY CHAR src1 NUM char src1[NUM];
字符型数组声明(长度为NUM)7 FUNC INT src1 int src1();
整型函数声明8 FUNC CHAR src1 char src1();
字符型函数声明9 FUNC VOID src1 void src1();
void型函数声明10 PARA INT src1
PARACHAR src1void func(int src1/char src1);
函数的参数声明11 result = src1 result = src1(普通赋值语句) 12 result[src2] = src1 result[src2] = src1(将值赋值给数组元素) 13 reslut = src1[src2] reslut = src1[src2](数组元素赋值给普通变量语句) 14 CALL src1 (result) 调用名字为src1的有返回值调用函数函数
(值参已经按顺序push到栈顶了),
如果result有标识符的名字,
则将该返回值赋值给result15 CALL src1 调用名字为src1的无返回值调用函数函数
(值参已经按顺序push到栈顶了)16 result = src1 <= src2 当src1 <= src2时,result = 1。否则result = 0 17 result = src1 < src2 当src1 < src2时,result = 1。否则result = 0 18 result = src1 >= src2 当src1 >= src2时,result = 1。否则result = 0 19 result = src1 > src2 当src1 > src2时,result = 1。否则result = 0 20 result = src1== src2 当src1 == src2时,result = 1。否则result = 0 21 result = src1 != src2 当src1 != src2时,result = 1。否则result = 0 22 EQU src1 0 src2 当scr1和0相等时,跳到scr2对应的地址 23 NEQ src1 0 src2 当scr1和0不等时,跳到scr2对应的地址 24 SET src1 声明src1对应的label 25 RT 无返回值函数返回语句 26 RT src1 有返回值函数返回语句,返回src1 27 SCAN result 将读入的值存到result中 28 PRINT src1 src2 src1存放字符串,src2存放的是表达式值,打印出来。
src1或scr2可以有一个为空29 result = src1 + src2 result = src1 + src2 30 result = src1 - src2 result = src1 - src2 31 result = src1 * src2 result = src1 * src2 32 result = src1 / src2 result = src1 / src2 33 GOTO src1 无条件跳转到src1处 34 END 程序结束 35 PUSH src1 将src1压进运行栈顶 36 GET src1[src2] 取数组元素
6.详细设计
具体请参看我的github账户: 点击打开链接