编译器

本文详细介绍了编译器构造的基础知识,包括文法设计、词法分析器、语法分析、语法制导及中间代码生成等内容,并探讨了目标代码生成的具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

<网页blog已经上线,一大波干货即将来袭:https://faiculty.com/>

编译器构造

一、设计文法

<func-body>::=<statement>
 <statement>::="{"<statement>"}" /*blockstatement*/   
                |[<type>] <identi> ["="<expr>]";" /*assignment*/ 
                |"return"<expr>";"   
                |"if""("<expr>")"<statement>["else"<statement>]  
                |"while""("<expr>")"<statement> |<expr>";"  

<expr>::=<bitwise-expr> 
            |<bitwise-expr>=<expr> 
<bitwise-expr>::=<eq-expr> 
            |<bitwise-expr>&<eq-expr> 
            |<bitwise-expr>|<eq-expr> 
<eq-expr>::=<rel-expr> 
            |<eq-expr>==<rel-expr> 
            |<eq-expr>!=<rel-expr> 
<rel-expr>::=<shift-expr> 
            |<rel-expr><<shift-expr> 
            |<rel-expr>><shift-expr> 
<shift-expr>::=<add-expr> 
            |<shift-expr><<<add-expr> 
            |<shift-expr>>><add-expr> 
<add-expr>::=<mul-expr> 
            |<add-expr>+<mul-expr> 
            |<add-expr>-<mul-expr> 
<mul-expr>::=<postfix-expr> 
            |<mul-expr>*<postfix-expri> 
            |<mul-expr>/<postfix-expri> 
<postfix-expr>::=<prim-expr> 
            |<postfix-expr>[<expr>] 
            |<postfix-expr>(<expr>{","<expr>}) 
<prim-expr>:=<number>|<ident>|<string>|"'"<char>"'"|"("<expr>")"

二、词法分析器

1.主要目标

这一个部分的主要内容是:
1. 根据输入的文件切割成一个个字符
2. 构造基本的符号表
3. 后期每次调用readToken函数得到一个token,然后给语法分析器。(这里不是一次性切割)

2.基本思想

利用文件指针一个个从源文件读一个字符,利用自动机判断, 切割出的单词放入 tok 字符数组中。这里我们将每个符号分为单独的一组,比如 标识符是一类、 ‘+’号、 ‘/’号等等一系列的符号都作为单独的一类,每种类别 都有一个 ID。调用该函数就能得到当前 token 的类别码 ID。

这里正规的分词是使用的自动机。

3.单词的分类

常见的分类是:
1. 标识符。用户给变量起的一些名字。
2. 常数。
3. 关键字。
4. 界符。包含单字界符(*,+….)和双字界符(:=, <=….)

本实验中,我们按如下分类:
1. 标识符。ID = 0,
2. 字符。ID = 1
3. 字符串。ID = 2
4. 数字(这里暂时只支持简单的整数)。ID = 3
5. 所有的关键字和界符进行排列。ID依次增加。

//关键词和界符
char *ch[31] ={"int", "char",  
        "main","void","if","else","while","return",
        "+","-","*","/","|","&","||","&&",">","<",">=","<=",
        "==","!=","=",",",";","(",")","{","}","[","]"};


/*
 * scanner : words
* */

identi:  ID is 00. // name of variable, can be characters, inter and _;

// const
characters: ID is 01.
string: ID is 02.
algrithem number: ID is 03.

keyWord: ID is 04 to 11:
         all the keyworlds as followed:  int char main void if else while   return  

//partion: 
algrithem operator: + - * /      12 to 15
logical operator: | & || && > < >= <= == !=  16 to 25 
others: = , ; ( ) { } [ ]    26 to 34

4.关键点

  1. 读单个字符用fgetc。
  2. 其中涉及到很多C的函数,isspace、isnumber等等。

其中可能涉及到小数的识别。
1. n:拼尾数值变量;每当读入尾数数字d时: n:=10*n+val(d);
2. m:小数位数变量;每当读入小数数字d时: m:=m+1;
3. num = n * 10 ^ (-m)

5.数据结构

/**  
*相应数据结构:
* 符号表:  struct SYNBL{
*              char name[100];
*              TYPE* type;           //只有当为数组时才有指向,如果只是Internet     char类型那么直接为NULL
*              char cat;              // 种类编码:a代表数组,i代表整型,c代表字符型
*              int offset;            //该变量起始偏移量
*              int memory;            //变量长度
*              }
*
* 类型表:   struct TYPE{
*              char cat;             // 种类编码:a代表数组,i代表整型,c代表字符  型..暂时不支持多维数组
*              ARRAY* array;         //数组表指针
*            }
*
* 数组表:  struct ARRAY{
*              int num;              //元素个数;
*              char cat;             //元素类型,i代表整型,c代表字符型;
*              int ArrayTM;           //占用总内存;
*            } 
*
* */

三、语法分析

1. 目标

其任务是识别和处理比单词更大的语法单位,如:程序设计语言中的表达式、各种说明和语句乃至全部源程序,指出其中的语法错误;必要时,可生成内部形式,便于下一阶段处理。

2.常见使用方法

2.1 递归子程序

算法:
对每一个非终结符,构造一个子程序,用以识别该非终结符所定义的符号串。每个子程序以产生式左部非终结符命名,以产生式右部构造子程序内容。

文法要求:
LL(1)文法

  1. 具有相同左部的产生式,首符号不同。 A -> a|a (首符号相同,×);
  2. 文法不能有左递归。 A -> A| (直接左递归,×)。

2.2 LL(1)分析法

LL(1)分析法的要求:
1. 利用一个分析表,登记如何选择产生式的知识;
2. 利用一个分析栈,记录分析过程;
3. 此分析法要求文法必须是 LL(1)文法。

算法:

  1. 先根据文法建立一个分析表。因为具有相同左部的产生式首符号不同,所以建立的表示唯一的。如上图。
  2. 然后利用栈对符号串进行语法分析。首先先把Z压入栈,再弹栈,读第一个单词,查找分析表,把相应的产生式逆序压栈。如果没有相应产生式,那么证明这句话有语法错误。如图:

LL(1)文法判定:

2.3 LR分析法

3.本实验

本实验中采用递归子程序法。如下图:

4.语义分析

语义分析主要包含语法制导和中间代码的生成。

5.语法制导

语法制导就是在原文法中加入语义动作符号,方便后续的中间代码生成。这里加入语义动作符号后,可以很方便的生成 逆波兰式。

6.中间代码生成

逆波兰式运算符进栈之日就是四元式生成之时。

7.设计方法

/**
* 中间代码数据结构:
* struct GEN_CODE{
*        char op[10];                         // operation, it's maybe a '*'    or '<' or 'if' and so on, "_" means nothing 
*        char R1[20];                         // operate object 1 
*        char R2[20];                         // operate object 2 
*        char des[20];                        // destination or answer of   this operation 
*  };  
*
* 中间代码生成方式:
* 1. a = b + c:
*    结果:(+, b, c, t)
*          (=, a, _, t)
*
* 2. a == b:
*    结果: (==, a, b, t)          // 这里t就是表达式的值
*
* 3. if(exp):
*    结果:(if, exp, _, _)
*
* 4. else{}:
*    结果:(else, _, _, _)
*
* 5. while(exp){}
*    结果:(while, _  , _, _)
*           ......
*          (do, exp, _, _)
*          ......
*          (whend,  _, _, _)
*
* 6. return i;
*    结果:(RET, i, _, _) 
*          
* 7. char a = 'c':
*    结果:(=, 'c', _, a)
*
* 8. int main(){}               //函数定义
*    结果:(main, _, _, _)
*
* 9. if or while语句块结束标签:
*    结果:(ifend, _, _, _)
*    结果:(whend, _, _, _)
*    结果:(elend, _, _, _)
*
**/

四、符号表

1. 作用

本实验中符号表主要用来预分配内存。也就是根据语义分析的结果,预先知道整个程序有多少个变量,给一个虚拟空间来分配内存。(这里只需要存贮用户定义的变量)

2.数据结构

/**  
    *相应数据结构:
    * 符号表:  struct SYNBL{
    *              char name[100];
    *              TYPE* type;           //只有当为数组时才有指向,如果只是 Internet    char类型那么直接为NULL
    *              char cat;              // 种类编码:a代表数组,i代表整型,c代    表字符型
    *              int offset;            //该变量起始偏移量
    *              int memory;            //变量长度
    *              }
    *
    * 类型表:   struct TYPE{
    *              char cat;             // 种类编码:a代表数组,i代表整型,c代表    字符  型..暂时不支持多维数组
    *              ARRAY* array;         //数组表指针
    *            }
    *
    * 数组表:  struct ARRAY{
    *              int num;              //元素个数;
    *              char cat;             //元素类型,i代表整型,c代表字符型;
    *              int ArrayTM;           //占用总内存;
    *            } 
    *
    * */

五、目标代码生成

这一部分主要功能是由语义生成的四元式生成可编译、可执行的汇编语言。主要 依据和处理事情是:
1. 根据符号表开辟运行空间,生成汇编语言头部
2. 扫描中间代码,生成相应的汇编语言段

1. 汇编语言生成规则

A. 环境:在本次课程设计中,我们采用的是 8086 汇编语言,主要使用到的 指令有:LEA、MOV、ADD、MUL、JMP、JE、SUB、CMP 等等。

B. 原则:每条四元式都会对应一段汇编语言,每次运算结束后都将变量存入 开辟的相应的地址中。所以没有相应的活跃信息表,不需要寄存器的调度算法。

2. 设计思路:

按照上述给出的四元式进行分类,每种类型的四元式对应一段具体的汇编代 码,具体请见四元式到汇编语言的翻译。其中需要解决的是回填问题,有以下几 类回填:
A. 在逻辑表达式中,如 a>b 中。我们将得到的四元式是: (>,a,b,t1),这里需要对 t1 进行赋值,当为真时赋值为 1,否 则为 0。 这里的不需要反填,按照具体汇编代码可以直接写出。
B. 在 if 语句中,如果不成立那么直接要跳转到 else 语句块或者 if 语句块结束 的下一条语句。处理如下:
1.遇见 if 保存当前汇编语言的标签,或者是当条四元式编号。
2.遇见 ifend 表示 if 语句块已经结束,此时添加一条空白汇编语言,待反 填。再加一条跳转汇编语句,如果执行到这,那么表示 if 条件成立,这里需要跳 转到 else 语句块的结束标签
3.遇见 else 反填 if 中的 JMP 4.遇见 elend 反填 ifend 中的 JMP

C. 在 while 语句块中,如果 do 成立则循环,否则跳出循环。处理如下: 1. 在 do 语句块结束处加上 JMP 跳转到 while 语句开始 2. 遇见 we,回填 do 中不成立时预留的 JMP

2.规则

/** 中间代码生成规则:
 * [a]: [0004H],其中4是指a的偏移量
 *                                                             //注意这里都是 16位的寄存器,对于int型,还有缺陷。可以改用32位
 * 
 * 0. 查询符号表,编写汇编头部:
 *                       DSEG  SEGMENT
 *                             ....
 *                             ....                          //为每个变量分配空 间,根据size
 *                       DSEG  ENDS
 *                       CSEG  SEGMENT
 *                             ASSUME: CS:CSEG, DS:DSEG
 *                      START: MOV  AX,DSEG
 *                             MOV  DS,AX
 *
 * 1. (=, 8, _, a)       --->   
 *                              MOV AX, 8           
 *                              MOV SI, a           
 *                              MOV [SI], AX           
 *
 *    (=, t, _, b)       --->   
 *                              MOV AX, [t]         
 *                              MOV SI, b
 *                              MOV [SI],AX
 * 2. (+, a, b, t0)      --->   
 *                              MOV AX, [a]                  //可以换成: MOV    AX,[a]  MOV BX, [b]  ADD AX,BX   注意乘法和除法
 *                              ADD AX, [b]
 *                              MOV SI, t0                   //t0 指偏移地址 
 *                              MOV [SI],AX
 * 3. (=, 'c', _, a)     --->   
 *                              MOV AL, 'c';        
 *                              MOV SI, a
 *                              MOV [SI],AL
 * 4. (==, a, b, t1)     --->   
 *                              LEA SI, a
 *                              MOV AX, [SI]
 *                              LEA SI,b
 *                              MOV BX, [SI]
 *                              CMP AX, BX          
 *                              MOV SI,t1
 *                              JE  Label_4_10
 *                              MOV AX,0
 *                              JMP Label_4_11
 *                  Label_4_10: MOV AX,1
 *                  Label_4_11: MOV [SI],AX
 *                              MOV SI, t1
 *                              MOV [SI], AX
 * 5. (>, a, b, t2)      --->                                    //这里暂时只    允许变量和 立即数
 *                              LEA SI, a
 *                              MOV AX, [SI]
 *                              LEA SI,b
 *                              MOV BX, [SI]
 *                              CMP AX, BX          
 *                              JG  Label_5_11       
 *                              MOV SI, t2
 *                              MOV AX,0
 *                              MOV [SI], AX
 *                              JMP Label_5_14                // 遇到这条语句 不用反填,直接就是当前语句编号+3就是他应该跳转的地方
 *                  Label_5_11: MOV SI, t2
 *                              MOV AX,1
 *                              MOV [SI],AX
 *                  Label_5_14: MOV AX,0
 *
 * 6. (if,t2, _, _)      --->                                 // 必须在中间代 码中设计一个语句块结束标签
 *                   Label_6_1: MOV AX, [t2]
 *                              CMP AX, 0
 *                              JE                            //else 的标签,
 *                    
 * 7. (ifend, _, _, _)   --->  
 *                   Label_7_1: MOV AX, 0                    //读到ifend或者    whend后就回填
 *                              JMP                           //else语句块结束   后的标签
 *
 * 8. (else, _, _, _)    --->
 *                   Label_8_1: MOV AX, 0
 *
 * 9. (elend,_,_,_)     --->
 *                   Label_9_1: MOV AX,0
 *
 * 10. (while, ,_,_)    ---> 
 *                   Label_10_1: MOV AX,0
 *                   
 * 11. (do, exp, _, _)  --->
 *                   Label_11_1: MOV AX, [t3]
 *                               CMP AX, 0
 *                               JE                           // while 语句块  结束标签
 * 12. (whend, _, _, _)  --->
 *                  Label_12_1: JMP                           // while 语句
 *                  Label_12_2: MOV AX,0
 *
 * 13. (RET, _, _, _)    --->
 *                  Label_13_1: INT 21H
 *                              CSEG ENDS
 *                              END  START

3.数据结构

/**
    * 汇编语言生成数据结构:
    *  1. 汇编代码栈:
    *     struct ASSEM{
    *        char lab[20];                                //label of this   instruction
    *        char instr[10];                              //instruction name
    *        char op1[20];                                // op1 of this    instruction
    *        char op2[20];                                // op2 of this    instrcution
    * };
    *  
    *  2. 转移指令反填栈:
    *     int INST                                           // 存的是待反填转移指   令的汇编语句编号,这里只有简单的反填。反填的是当前汇编语句的lab
    *
    *     反填规则:
    *     遇到:(ifend, _, _, _)
    *        (else, _, _, _)
    *        (whend, _, _, _)
    * */
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值