前端:词法分析--》语法分析--》语义分析
后端:中间代码--》优化:(平台无关优化)(平台相关优化)--》对应平台的指令
基本要求:实现词法分析,语法分析;识别注释,标识符(给出提示);给出错误提示信息;
把语句划分为有意义的单元,把这些单元叫词素,一个字符一个字符读把组合在一起来判断词素;
词素的分类(把词素划分为不同的类):词素 +词素的额外信息(类型)---》token;
输入:源代码 输出:token序列(队列)
单个字符:( , [ , ] , );token{ "(",LEFT_PARENSIS(枚举值) };
两个字符:+ =
两个以上的字符:interesting(标识符);
要注意标识符的命名规则:以数字或下划线开头,可以包含数字下划线字母,其他字符标识标识符的结尾;
有限状态机:(switch +while+if)
int i = 10;
以0为起始状态,通过getchar----(i)--》假设进入标识符状态,继续读n,读t,认为是标识符,遇到空格结束回到状态0,遇到等号进入赋值状态,再遇到等号进入等号状态,空格回到状态0;
(先不考虑浮点数)遇到数字1进入数字状态;
如何区分标识符与关键字:
用哈希表存放关键字,判断这个标识符在不在哈希表中,存在则改成关键字;
{“int”, KEYWORD}
{“i” ,IDENFIER}
{“=”,ASSIGN}
{“10”,NUMBER}
{“;” ,SEMICOLON}
进阶要求,给出错误提示,(出错的行数的信息)用num记录当前的行数,出错就打印行数;
-------------------------------------------
读取token序列,生成抽象语法树;
根据汉语的语法规则生成语法树;
表达式(把表达式组合成数的结构):
1+2*3
怎么去表示这个语法(巴斯柯德语法表示):
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运行原理:
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;栈++;
运算:
分支跳转:
JMP / JZ (判断ax是不是等于0,等于0就jump) /JNZ(与jz相反);
内存空间:
为什么要有stack?(code/data)
假如没有stack,怎么实现函数调用?
函数调用需要的条件:函数地址,参数值,返回值(可以让寄存器ax存),返回地址;
计算机的复杂可以增加一个中间层或抽象层来简化;
函数是后进先出的用栈描述:
不需要函数调用的编译器:
函数跳转:
NVAR(申请一个初始空间存MAIN 的bp地址)为了恢复调用之前的状态;
ret,NVAR1(存ret);
LEA - 1;
push +;
LEA3;
LI
push+
LEA2;
LI
ADD
SI
把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(全局变量)
就把当成num
type:char int ptr(指针)
value: io 地址 value取决于type和class
G开头的存全局变量的属性,等函数退出了再写回原来属性;
Gclass:
Gtype:
Gvalue:
例子:
提前处理关键字,存入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
--------------------------
语法(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语言文法的子集:
代码:
文法:
program = var_decl(变量声明) | fun_decl(函数声明)
var_decl = enum_decl | type id;
fun_decl = type id(peram(参数) ){ 语句(statement) };
enum_decl = enum[id] { id,id...}
递归下降(根据文法解析代码):
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的函数;
不用递归下降解析表达式,用优先级爬山解析表达式;相当于把表达式当作终结符,
-----------------------
语句(statement):if else while return
表达式(expression):2+3 4*5(表达式的目的求值)
赋值语句可以是语句也可以是表达式;
对于本项目a = 3也是表达式;
操作符优先级(operator precedence)
3 + 4*3 -5
CPC operator precedence
一元 二元 三元
1、++ -- = ? :
2、+ - || &&
3、~ ! | & ^
4、& * == > < >= <= << >>
5、() + -
后置++ -- * / %
[ ]
一元操作符按出现的顺序的逆序优先处理,括号最高
一元大于二元,一元前四个相等,二元最高【】,赋值最低,
--------------------------------------------------------------------------------------------------------------------
* ++ a++ = i == 0 ?2+3 :4*5;(int *a; i = 0)
-=-===================
stack和register为初始化状态;symbolTable解析完就没用了;
字面量定义在data区;
预处理把main存入symbolTable,code区的Nvar会提前占用一个位置给局部变量,可用于存入函数,
后面再遇到这个函数,直接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区的变量和字面量;