基于C0文法设计的编译器

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 src1

void 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有标识符的名字,
则将该返回值赋值给result
15 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账户: 点击打开链接



猜你喜欢

转载自blog.csdn.net/weixin_41400276/article/details/79113848