编译器项目

前端:词法分析--》语法分析--》语义分析

后端:中间代码--》优化:(平台无关优化)(平台相关优化)--》对应平台的指令

编译器项目_第1张图片

基本要求:实现词法分析,语法分析;识别注释,标识符(给出提示);给出错误提示信息;

1、词法分析(目的:生成token序列):
de73020063fa400a82511f8e907c4db2.png

把语句划分为有意义的单元,把这些单元叫词素,一个字符一个字符读把组合在一起来判断词素;

词素的分类(把词素划分为不同的类):词素 +词素的额外信息(类型)---》token;

输入:源代码   输出:token序列(队列)

单个字符:( , [ , ] , );token{ "(",LEFT_PARENSIS(枚举值) };

编译器项目_第2张图片

两个字符:+ =

两个以上的字符:interesting(标识符);

要注意标识符的命名规则:以数字或下划线开头,可以包含数字下划线字母,其他字符标识标识符的结尾;

有限状态机:(switch +while+if)

int       i           =      10;

编译器项目_第3张图片

以0为起始状态,通过getchar----(i)--》假设进入标识符状态,继续读n,读t,认为是标识符,遇到空格结束回到状态0,遇到等号进入赋值状态,再遇到等号进入等号状态,空格回到状态0;

(先不考虑浮点数)遇到数字1进入数字状态;

如何区分标识符与关键字:

        用哈希表存放关键字,判断这个标识符在不在哈希表中,存在则改成关键字;

{“int”,   KEYWORD}

{“i” ,IDENFIER}

{“=”,ASSIGN}

{“10”,NUMBER}

{“;” ,SEMICOLON}

编译器项目_第4张图片

进阶要求,给出错误提示,(出错的行数的信息)用num记录当前的行数,出错就打印行数;

-------------------------------------------

2、语法分析(读取token序列,模式匹配的过程)

读取token序列,生成抽象语法树;

b18a010918424f4dba33493ccbb378f4.png

根据汉语的语法规则生成语法树;

编译器项目_第5张图片

表达式(把表达式组合成数的结构):

1+2*3

编译器项目_第6张图片

怎么去表示这个语法(巴斯柯德语法表示):

ADD(加法表达式)---》mul   |(或) add + mul       //只考虑加法和乘法;

mul(乘法表达式)---》 pri(数字或变量)   |    mul * pri;

pri  --》 identifier  | number   | (add);

先判断是不是乘法表达式,遇到加号判断出加法表达式,遇到pri,再判断是不是乘法表达式;以乘法表达式的规则进行匹配;用递归进行匹配;(递归下降算法);

文法(怎么去表示文法)?:

------------------------------------------------------------------------------------

代码实现编译器:

one -》pass;局部变量只能定义在函数开始的位置

vm设计:计算基于register&stack;

register:pc(代码指针,指向正在执行的代码区),sp(基指针寄存器函数调用保留当前栈帧和栈顶),bp(追踪栈顶数据,出栈入栈),ax(通用寄存器,数据寄存器);基于栈的虚拟机;

stack pointer:维护这个栈顶

base pointer:维护上一个栈顶

mem(内存):code  ,data (静态数据,字面量),stack

内存空间包括:code,data,堆,stack,内核(自下往上);

指令集:save & load,运算,分支跳转,native——call(io,内存);

内存到寄存器(load)寄存器到内存(save)

四则运算,位运算,逻辑运算,

判断循环,brunch,jump,cmp;

Io:print/open/write/read

动态:malloc/free/memset;

VM运行原理:

编译器项目_第7张图片

VM指令集:

        1、Save & Load:IMM(立即加载一个数据到寄存器),LEA(加载一个地址),LC( load char),LI(load int ),SC(save char),SI(save int),PUSH(将寄存器数据推到栈顶);

        2、运算:ADD,SUB,MUL(乘),DIV(除),MOD(取模)//,OR(或),xOR(异或),AND(与),SHL(左移),SHR(右移),IQ(等),NE(不等),LT(小于),LE(小于等于),GT(大于),GE(大于等于)

        3、分支跳转:JMP(将pc指针跳转到指定的代码区域),JZ(jump zero,基于寄存器的值判断是否要jump,以及jump到什么位置),JNZ(jump not zero),CALL(call),NVAR(new stack framework),DARG(delete stack framefor),RET(return);

        4,Native-call:open/  close /read   /prtf   /mall  /free  /mset  /mcmp  /    exit;

Save &Load:

        IMM2(把2加载到ax寄存器)

        LEA(基于栈的bp为基准操作)LEA-1就是bp的下一个位置;

        LOAD(把ax的地址对应的值加到ax里)

        sp指向栈顶所指向的地址,把地址转化为指针,对地址取derefernce;栈++;

PUSH(到栈顶,sp- -);编译器项目_第8张图片

运算:

编译器项目_第9张图片

分支跳转:

JMP  /   JZ (判断ax是不是等于0,等于0就jump)  /JNZ(与jz相反);

编译器项目_第10张图片

内存空间:

        为什么要有stack?(code/data)

假如没有stack,怎么实现函数调用?

编译器项目_第11张图片

函数调用需要的条件:函数地址,参数值,返回值(可以让寄存器ax存),返回地址;

计算机的复杂可以增加一个中间层或抽象层来简化;

函数是后进先出的用栈描述:

编译器项目_第12张图片

不需要函数调用的编译器:

编译器项目_第13张图片

函数跳转:

       编译器项目_第14张图片 VM指令:

NVAR(申请一个初始空间存MAIN 的bp地址)为了恢复调用之前的状态;

ret,NVAR1(存ret);

LEA - 1;

push +;

LEA3;

LI

push+

LEA2;

LI

ADD
SI

编译器项目_第15张图片

把ax暂存与栈里,ax空闲,使用add计算;

词法分析:

需要识别的东西:

        keyword:if        els        while        char        int        char*        goto        printf

        identifiers(识别):变量        函数        

        literals (字面量):a = 3(3就是字面量)printf(“hello world”)字符串字面量

        operators(运算符):+  -   *  /    >   <  =  {  [   ]  }   

        stop words: 注释符  空格,换行   

tokenize方法:读源码,输出分词;

         ·返回值:token/token_value        因为返回值是两个所以不用函数返回,用全局变量标识

        ··变量函数:声明定义使用(保证声明定义在使用之前,在声明和定义之前做出很充分的解析,解析完把内容存到symbol table(符号表)里,在使用时找到)

symbolTable(符号表):

        symbol:keywords  / identifier

        hash:给符号一个哈希值,哈希值相同再比较name提高查找速率;

        name:

        class:Num/Fum/Sys/Loc(局部变量)/Global(全局变量)        

如果用了枚举【编译器项目_第16张图片

就把当成num        

        type:char        int         ptr(指针)

        value: io        地址        value取决于type和class

c语言的局部变量的遮蔽(编译器项目_第17张图片

G开头的存全局变量的属性,等函数退出了再写回原来属性;

Gclass:

Gtype:

Gvalue:

例子:

编译器项目_第18张图片

提前处理关键字,存入symbolTable,边解析边生成vm指令

token        name                class        type        value        Glo

char        "char"        

int             "int"        

return        " return"        

id                "a"                  Glo            ptr        0                (在函数中找到a,把全局遮蔽)

id                "add"                Fun         Int         pc(地址)

id                “b”                    loc              int                0

编译器项目_第19张图片

编译器项目_第20张图片

编译器项目_第21张图片

编译器项目_第22张图片

--------------------------

文法分析与递归下降:

        语法(syntactic):单词怎么组成句子        lexeme(词素):句子的最小单元,词素怎么组成句子,组成句子的规则叫syntactic;

        token和lexeme的区别:

int a;int b;a和b是一类lexeme,这一类的词素是token(相当于lexeme的代号,token是类型,而lexemem是实例);

        语义(semantic):语法背后实际的含义;int i ; i = 0 ; i++;(语义无误);float i;i = 2.23;i++;(语义有误);

        文法(Grammer):语法的合集,以某种表现形式;可以根据文法解析对应语言的所有语句;

cpc(c4)文法是c语言文法的子集:

代码:

编译器项目_第23张图片

文法:

        program = var_decl(变量声明) | fun_decl(函数声明)

        var_decl = enum_decl | type id;

        fun_decl = type id(peram(参数) ){ 语句(statement) };

        enum_decl = enum[id] { id,id...}

编译器项目_第24张图片

递归下降(根据文法解析代码):

编译器项目_第25张图片

S= aS  | bS`(S是开始符,代码的整体就是S,对应上文的program,后面的规则叫过程)

S`=a | $;(a或b小写字符叫终结符,token不可再分,$表示空)

S`可能是a或者空;

parse_s(token){     

        if token == 'a' {

                parse_s(token++)

        }elseif token == 'b'{parse_s`(token++);

        else{panic(sytax_error)}

parse_S`(token){

        if token =='a'{success

        }else token =='end of file'{success

        }else{panic(sytax_error)};

每一层解析都处理掉一个token,直到为空;

要解析一个代码,要把文法里的每一个规则都要写一个parse_xx的函数;

编译器项目_第26张图片

不用递归下降解析表达式,用优先级爬山解析表达式;相当于把表达式当作终结符,

编译器项目_第27张图片

编译器项目_第28张图片

编译器项目_第29张图片

-----------------------

表达式求值:

        语句(statement):if        else        while         return

        表达式(expression):2+3         4*5(表达式的目的求值)

赋值语句可以是语句也可以是表达式;

编译器项目_第30张图片

对于本项目a = 3也是表达式;

操作符优先级(operator precedence)

3  +  4*3  -5

CPC  operator precedence

一元                                                二元                                                        三元

1、++  --                                        =                                                              ?  :

2、+   -                                          ||   &&   

3、~   !                                        |    &     ^

4、&   *                                        ==  >  <  >=  <=  <<  >>

5、()                                            + -

后置++ --                                *   /    %

                                                    [  ]

一元操作符按出现的顺序的逆序优先处理,括号最高

一元大于二元,一元前四个相等,二元最高【】,赋值最低,

--------------------------------------------------------------------------------------------------------------------

优先级爬山:(遇到操作符先把操作符缓存,遇到数据单独缓存,直到遇到一个操作符比目前已经缓存下来的操作符优先级更低,就把最高优先级的操作符处理掉,表达式结束就依次出栈)

编译器项目_第31张图片

* ++ a++ = i == 0 ?2+3 :4*5;(int *a;  i = 0)

编译器项目_第32张图片

编译器项目_第33张图片

编译器项目_第34张图片

编译器项目_第35张图片

编译器项目_第36张图片

编译器项目_第37张图片

编译器项目_第38张图片

-=-===================

代码生成:(解析完源代码,生成vm指令后会得到的code区,data区)

        stack和register为初始化状态;symbolTable解析完就没用了;

        编译器项目_第39张图片

字面量定义在data区;

预处理把main存入symbolTable,code区的Nvar会提前占用一个位置给局部变量,可用于存入函数,

编译器项目_第40张图片

后面再遇到这个函数,直接call在code区的地址;

全部变量在data区,data区会预留一个Glo地址来存,

栈以bp为分割点,bp上为栈,下为局部变量;LEA命令基于bp,LEA3调用第一个参数,第二个LEA2;

parse&codegen:

var,fun-》parse_par(参数列表),parse-fun(函数)————》parse_statement--->parse_exper

生成code区所有指令,data区的变量和字面量;

你可能感兴趣的:(c语言)