其编译过程采用一趟扫描方式
以语法、语义分析程序为核心
词法分析程序和代码生成程序都作为一个过程,当语法分析需要读单词时就调用词法分析程序,而当语法、语义分析正确,需要生成相应的目标代码时,则调用代码生成程序。
表格管理程序实现变量,常量和过程标识符的信息的登录与查找。
出错处理程序,对词法和语法、语义分析遇到的错误给出在源程序中出错的位置和与错误 性质有关的编号,并进行错误恢复。
<程序> :: = <程序首部> <分程序>.
<程序首部> :: = PROGRAM <标识符>;
<分程序> :: = [<常量说明部分>][<变量说明部分>][<过程说明部分>]<语句部分>
<常量说明部分> :: = CONST <常量定义>{ ,<常量定义> };
<常量定义> :: = <标识符 >= <无符号整数>
<变量说明部分> :: = VAR <标识符>{ ,<标识符> };
<过程说明部分> :: = <过程首部>; <分程序>;
<过程首部> :: = PROCEDURE <标识符>//变量定义
<语句部分> :: = <语句> | <复合语句>
<复合语句> :: = BEGIN <语句>{ ; <语句> }
<语句>:: = <赋值语句> | <条件语句> | <当型 循环语句> | <过程调用语句> | <读语句> | <写语句> | <复合语句>
<赋值语句> :: = <标识符>: = <表达式>
<读语句> :: = READ(<标识符>{, <标识符>})
<写语句> :: = WRITE(<表达式>{, <表达式>})
<过程调用语句> :: = CALL <标识符>
<条件语句> :: = IF <条件> THEN <语句>
<当型循环语句> :: = WHILE <条件> DO <语句>
<因子> :: = <标识符> | <常量> | (<表达式>)
<项> :: = <因子>{ <乘法运算符><因子> }
<乘法运算符> :: = *| /
<表达式> :: = [+| -]<项>{<加法运算符><项>}
<加法运算符> :: = +| -
<条件> :: = <表达式><关系运算符><表达式> | ODD <表达式>
<关系运算符> :: = # |= | > | >= | < | <=
词法分析器能够读入使用PL/0语言书写的源程序,完成的任务包括读源程序(getch),滤空格,识别保留字,识别标识符,拼数,识别单字符单词,拼双字符单词,输出单词符号串及其属性到一中间文件中,具有出错处理分析功能,可以给出词法错误提示。
1)主程序流程图
2)数字处理程序
3)其他字符处理程序
4)空格处理程序
1)数据结构定义
typedef struct DisplayTable {
int Index; //标识符所在表的下标
int type; //标识符的类型
int line; //标识符所在表的行数
char symbol[20]; //标识符所在表的名称
}Table;
2)函数定义
int isLetter(char ch1)// 判断该字符是否为字母
int isDigit(char ch1)// 判断该字符是否为数字
string itos1(int n) //把整数n转换成string型
void error(char str[20], int nLine, int errorType)//出错处理
void getsym(char ch[], int chLen, Table table[Max], int nLine)//词法分析程序
语法分析的任务是识别由词法分析给出的单词符号序列在结构上是否符合给定的文法规则。PL/0编译程序的语法分析采用自顶向下的递归子程序法:对应每个非终结符语法单元,编一个独立的处理过程(或子程序)。 语法分析从读入第一个单词开始由非终结符‘程序’即开始符出发,沿语法描述图箭头方向进行分析。 当遇到非终结符时,调用相应的处理过程。从语法描述图看也就进入了一个语法单元,再沿当前所进入的语法描述图的箭头方向进行分析。当遇到描述图中的终结符时,则判断当前读入的单词是否与图中的终结符相匹配,若匹配,则执行相应的语义程序(就是翻译程序),再读下一个单词继续分析。遇到分支点时将当前的单词与分支点上的多个终结符逐个相比较,若不匹配时可能是进入下一个非终结符语法单元或是出错。如果一个PL/0语言的单词序列在整个语法分析中,都能逐个得到匹配,则语法分析正确。对于正确的语法分析作相应的语义翻译,最终得到目标程序。
1)主程序流程图
2)分程序处理过程
3)常量说明部分
4)常量定义
5)变量说明
6)过程说明
7)过程首部
8)语句部分
9)语句判断
10)赋值语句
11)while语句
12)If语句
13)Write语句
14)Read语句
15)Call语句
16)表达式判断
17)项判断
18)因子判断
19)条件判断
void getWord(Table table[255], int count);//读入词法分析结果
//void addtotable(string name, string kind, int val, int lev, int adr, int size); //把记录加入符号表
string find(string s);//匹配单词 在符号表中查找 返回其类型
void initmp();//初始化保留字,界符对照表
void printGrammar(int x, int y, string statement);//打印语法分析结果
void printGrammerErrors();//打印语法出错记录
int error(int e, int eline);//错误处理
bool nexteql(string s);;//从词法分析的结果顶部取出一个单词,判断s是否与之相等,不相等返回FALSE,相等返回TRUE并且顶部指针指向词法分析结果的下一个单词
string itos(int n);//把整数n转换成string型
int stoint(string s); //把字符串转换成整数
bool isProc(int lev);//是否为程序
bool isPartProc(string name,int lev);//是否为分程序
bool isProcPrelude();//是否为程序首部
bool isConExplain();//是否为常量说明
bool isVarExplain(string outer, int lev);//是否为变量说明
bool isProcessExplain(string name,int lev);//是否为过程说明
bool isStatementPart(int lev);//是否为语句部分
bool isConDefin();//是否为常量定义
bool isProcessPrelude(string name,int lev);//是否为过程首部
bool isStatement(int lev);//是否为语句
bool isAssStatement(int lev);//是否为赋值语句
bool isWhileStatement(int lev);//是否为While语句
bool isIfStatement(int lev);//是否为if语句
bool isCallStatement(int lev);//是否为Call语句
bool isWriteStatement(int lev);//是否为Write语句
bool isReadStatement(int lev);//是否为read语句
bool isCondition(int lev);//是否为条件
bool isFactor(int lev);//是否为因子
bool isItem(int lev);//是否为项
bool isExpression(int lev);//是否为表达式
其中: F段代表伪操作码
L段代表调用层与说明层的层差值
A段代表位移量(相对地址)
指令功能表:
LIT 0 a 将常数值取值到栈顶 a为常数值
LOD l a 将变量值取值到栈顶 a为偏移量 l为层差
STO l a 将栈顶内容送入某变量单元中 a为偏移量 l为层差
CAL l a 调用过程 a为过程地址 l为层差
INT 0 a 在运行栈中为被调用的过程开辟a个单元的数据区
JMP 0 a 无条件跳转至a地址
JPC 0 a 条件为假 跳转至a地址 否则顺序执行
OPR 0 0 过程结束调用 返回调用点并退栈
OPR 0 1 栈顶元素取反
OPR 0 2 次栈顶与栈顶相加 退栈两个元素 结果进栈
OPR 0 3 次栈顶减栈顶 退栈两个元素 结果进栈
OPR 0 4 次栈顶乘以栈顶 退栈两个元素 结果进栈
OPR 0 5 次栈顶除以栈顶 退栈两个元素 结果进栈
OPR 0 6 栈顶元素奇偶判断 结果进栈顶
OPR 0 7
OPR 0 8 是否相等 退栈两个元素 结果进栈
OPR 0 9 是否不等 退栈两个元素 结果进栈
OPR 0 10 次栈顶是否小于栈顶 退栈两个元素 结果进栈
OPR 0 11 次栈顶是否大于等于栈顶 退栈两个元素 结果进栈
OPR 0 12 次栈顶是否大于栈顶 退栈两个元素 结果进栈
OPR 0 13 次栈顶是否小于等于栈顶 退栈两个元素 结果进栈
OPR 0 14 栈顶值输出至屏幕
OPR 0 15 屏幕输出换行
OPR 0 16 从命令行读入一个输入至于栈顶
1)符号表定义:
typedef struct t
{
string name;//名字
string kind;//类型
int val;//值
int lev;//嵌套层数
int adr;//相对地址
int size;//存储空间
string directouter;//直接外层
int paranum;//过程含参数个数(只有过程存在该参数)
}symTable;//符号表定义
2)符号表生成过程
符号表中保存的项有:1.常量,2.变量,3.过程
1.常量,在遍历过程中,遇到常量定义,将常量写入符号表中,相对地址为-1
2.变量,在遍历过程中,遇到变量定义,将变量写入符号表中,相对地址为pardr,(每一个子程序中逻辑地址从3开始起始,依次累加)
3.过程,在遍历过程中,先保存过程中以变量形式有的所调用的参数,地址依次累加,最后再保存过程名,相对地址为padr(从主程序过程名起为0,依次累加),同时再对当前过程加入符号表时,要更新其直接外部所占用的空间大小(如果当前过程中有变量存在的话,直接外部需要增加自己的size大小),并维持一个指针指向最新的过程再符号表中的位置。同时更新新的过程中的变量起始逻辑地址为3
代码实现:
if (kind == "PROCEDURE"){
//如果符号表中加入“过程项”,需要更新上一个过程需要的空间(直接外部)
plev = lev, pradr = 3;//如果有了新的过程,当前过程中定义的变量的起始地址重新置为3
if (tabletop>0){
if (symtable[tabletop - 2].adr == -1){
//如果过程中存在参数,则调用的最后一个参数再符号表中为位置为tabletop-2,如果该项地址为1,说明为常量,不存在参数
symtable[ltx].size = 3;
}
else
symtable[ltx].size = 1 + symtable[tabletop - 2].adr;
//调用当前过程的直接外部过程需要的空间为1(过程自身)+截至到最后一个变量需要的空间(逻辑地址)
}
ltx = tabletop - 1;//更行为最新的过程
}
注:此部分的所有操作都是在第三部分语义分析完全正确的情况下进行,具体模块/语句的判断键语义分析
bool isPartProc(string name,int lev){
//是否为分程序
int i;
int dx;//名字分配到的相对地址
int tx0;//保留初始tx
int cx0;//保留初始cx
dx = 3;//相对地址从3开始,前3个单元即0、1、2单元分别为 SL:静态链;DL:动态链;RA:返回地址
tx0 = tabletop;//记录本层的初始位置(符号表中的位置)
if (flag2 != 1)gen(jmp, 0, 0);//如果语法分析没有出现错误,产生转移代码
while (isConExplain());//是否为常量说明
while (isVarExplain(name,lev));//是否为变量说明
while (isProcessExplain(name,lev));//是否为过程说明(可能会存在嵌套过程)
//这句结束之后符号表的最高位应该为当前程序名对应的项,最开始生成的无条件跳转代码应该跳转到这里
//取当前代码位置,回填初始跳转指令a段,生成开辟空间代码
code[symtable[tx0 - 1].adr].positionSet = cx;//开始生成当前过程代码
cx0 = cx;//结束嵌套 记录当前位置
dx = symtable[tx0 - 1].size;//当前层程序名或过程名需要的size
if (flag2 != 1)gen(inte, 0, dx);//生成分配内存代码 在运行栈中为被调用的过程开辟a个单元的数据区
if (isStatementPart(lev)){
//语句过程执行结束
//每个过程出口都要使用的释放数据指令//结束过程调用,返回结束点并退栈
if (flag2 != 1)gen(opr, 0, 0);
return true;
}
else{
error(8, line);//报错 没有语句说明部分8
return true;
}
}
4.IF语句目标代码生成过程:
5.其余语句目标代码:
其余语句目不涉及跳转指令,不需要保存地址,对栈顶元素进行操作,根据opr指令操作即可。
写语句:将输入的值存入栈顶,再将栈顶元素压入对应变量的val中。
读语句:将对应的变量/常量的值读出到栈顶。
条件判断语句,对栈顶元素和此栈顶元素提取出来进行大于 小于,等于,奇偶等判断,将结果入栈。
计算语句,对栈顶元素与次栈顶元素进行加减乘除取反等,结果存入栈顶。