PL/0编译程序的研究与改进
图片太多了,报告完整下载见链接:
2 PL/0总体结构
PL/0编译程序采用单遍扫描方式的编译过程,由词法分析程序、语法语义分析程序以及代码生成程序3个独立的过程组成。PL/0编译程序以语法语义分析程序为核心,当语法分析需要读单词时就调用词法分析程序,而当语法语义分析正确需生成相应语言成分的目标代码时,就调用代码生成程序。PL/0编译程序的组织结构如图1.1所示。
图2-1 PL/0编译程序组织结构
当源程序编译有错时,PL/0编译程序用出错处理程序对词法和语法语义分析遇到的错误给出在源程序中出错的位置和错误性质。当源程序编译正确时,编译程序正常结束,可输出相应的类P-code目标程序。
图2-2 PL/0编译程序的整体结构图
3 头文件pl0.h
主要定义了程序中用到的各数据结构,说明了程序中出现的函数。
3.1数据结构
数据结构分析和综合时所用的主要数据结是符号表和虚拟机代码结构。符号表由源程序中所用的标识符连同它们的属性组成,其中属性包括名称,种类(如变量、数组、过程,常量等)、标识符的层次相对地址和数据区大小。虚拟机代码结构中包括虚拟机代码指令,引用层与声明层的层次差,以及具体调用的指令操作。并据此构造了一个数组,来存储目标代码。
3.2变量
变量是简单整型变量。需在变量说明部分进行说明(var),但无须给出数据类型,因为只有整型变量。
3.3函数说明
表3-1 函数功能表
函数名 |
功能简要说明 |
void error(int n, int line) |
出错处理函数,打印出错信息,错误总数加1。 |
int getch() |
读取字符函数,返回字符。 |
int getsym() |
读取下一单词符号 |
int position(char* idt, int tx); |
字符在符号表中位置查询函数返回值:返回标识符在符号表中的索引 |
int gen(enum fct x, int y,int z); |
生成目标代码,并送入目标程序区 |
int test(bool* s1, bool* s2,int n); |
测试当前符号是否合法,若不合法,打印出错信息并进行跳读 |
void enter(enum object k, int* ptx,int lev, int* pdx); |
在符号表中登录分程序说明部分出现的名字 |
int constdeclaration(int* ptx,int lev,int* pdx) ; |
处理常量说明,并将常量名及相应信息填入符号表 |
int vardeclaration(int* ptx,int lev, int* pdx); |
处理变量说明,并将变量名及相应信息填入符号表 |
int statement(bool* fsys, int* ptx,int lev) ; |
分析处理各种语句 |
int condition(bool* fsys,int* ptx,int lev); |
分析处理条件式 |
void listcode(int cx0); |
打印目标代码 |
int block(int lev, int tx, bool* fsys); |
PL0编译器对外的接口,分析处理程序 |
void interpret() ; |
解释执行目标代码 |
int base(int l,int* s,int b) ; |
通过静态链求出数据区的基地址 |
int expression(bool* fsys,int* ptx,int lev) |
表达式处理,由参数返回结果类型 |
int term(bool* fsys,int* ptx,int lev); |
项处理,由参数返回结果类型 |
int factor(bool* fsys,int* ptx,int lev) ; |
因子处理,由参数返回结果类型 |
int inset(int e, bool* s); int addset(bool* sr, bool* s1, bool*s2,int n); int subset(bool* sr, bool* s1, bool*s2,int n); int mulset(bool* sr, bool* s1, bool*s2,int n); |
使用数组实现集合的集合运算 |
3.4指令说明
表3-2指令功能表
指令名 |
功能 |
LIT |
将常量值取到运行栈顶. |
LOD |
将变量放到运行栈顶. |
STO |
将栈顶的内容送入某变量单元中. |
CAL |
调用过程的指令. |
INT |
为被调用的过程(或主程序)在运行栈中开辟数据区. |
JMP |
无条件转移指令. |
JPC |
条件转移指令,当栈顶的布尔值为真时,顺序执行,否则转向域的地址. |
OPR |
系运算符和算术运算指令.将栈顶和次栈顶的内容进行运算,结果存放栈顶. |
4源文件pl0.c
4.1初始化
进行运行前初始化,对保留字表 (word)、保留字表中每一个保留字对应的 symbol 类型 ( wsym )、部分符号对应的 symbol 类型表 ( ssym )、类 PCODE 指令助记符表 ( mnemonic )、开始以及后跟符号集合 ( declbegsys、statbegsys等)以及一些全局变量的初始化
4.2主函数
函数在打开pl0源程序文件后,main函数调用block进行分析,生成fa1.tmp等文件,其中fa.pcode文件输出虚拟机代码,若无错误则会调用解释器进行解释。
4.3词法分析
词法分析的任务是对由字符组成的单词进行处理,从左至右逐个字符地对源程序进行扫描,产生一个个的单词符号,把作为字符串的源程序改造成为单词符号串的中间程序。执行词法分析的程序称为词法分析程序或扫描器。
主要调用的函数有:
(1)getch()函数
主要功能为读取一个字符放在全局变量ch里面,同时输出源代码以及行号。该函数读取PL/0文件中一行,存入line缓冲区,line被getsym取空后再读一行。每次从line缓冲区读出一个字符放在全局变量ch里面,被函数getsym调用。
图4-1 getch()函数的执行流程图
(2)getsym()函数
对于调用的getch()函数得到的一个串也即一个单词,识别它的类型为保留字、标识符、数字或是其它符号。
图4-2 getsym()函数的执行流程图
表4-1 单词类别表
单词类别 |
单词内容 |
保留字 |
begin、end、if、then..... |
运算符及界符 |
:= ,+, - ,*, / ,<, <=, = >,=, > ,#....... |
标识符 |
自定义的变量名、常数名,过程名 |
常数 |
10,25等整数 |
界符 |
;,( ,) ..... |
如果是保留字,把全局变量enum symbol sym 置成相应的保留字类型。如果是标识符,把 sym 置成 ident 表示是标识符,于此同时,全局变量char id[al+1]( al为标识符的最大长度)中存放的即为保留字字符串或标识符名字。如果是数字,把 sym 置为 number,同时 全局变量int num中存放该数字的值。如果是其它的操作符,则直接把 sym 置成相应类型。经过本函数后ch 变量中存放的是下一个即将被识别的字符。
char ch; /* 获取字符的缓冲区,getch 使用 */
enum symbol sym; /* 当前的符号 */
char id[al+1]; /* 当前ident, 多出的一个字节用于存放0 */
int num; /* 当前number */
单词的识别可以借助于状态转换图进行设计,状态转换图类似于有限状态自动机,识别标识符单词、数字单词、单字符单词和双字符单词的状态转换图如图2.1所示保留字单词的识别可以在识别标识符单词的基础上,再去检索保留字表。
图4-3 PL/0状态转换图
4.4语法语义分析
4.4.1简介
语法分析主要根据PL/0的EBNF表示进行自顶向下分析,使用递归子程序法对每个语法单位编写分析程序,实现语法分析。合法的PL/0程序都可以对应一颗自顶向下构造的语法树。语法分析树的根节点为<程序>,叶子节点为构成源程序的单词,每个内部节点代表构成源程序的各种不同的语法单位。
4.4.2相关函数
block()函数是PL/0语法分析程序的入口,是编译程序主模块,程序的核心所在,lev参数为当前分程序所在层,tx为符号表当前尾指针,fsys为当前模块后跟符号集。
int block(int lev, int tx, bool* fsys);
图4-4 block流程图
根据接收的sym的的符号类型,调用不同的处理函数,并对可能出现的错误进行错误处理。若sym中是constsym常量声明符号,则调用constdeclarationdo()函数;若是varsym变量声明符号,调用vardeclarationdo()函数;若是procsym过程声明符号,调用enter()函数,在符号表中插入一项。在constdeclarationdo()函数和vardeclarationdo()函数中也调用了enter()函数在符号表中插入一项。
4.4.3语法描述图
程序语法描述
图4-5程序语法描述图
分程序语法描述
图4-6分程序语法描述图
语句语法描述
图4-7 语句语法描述图
条件语句语法描
图4-8条件语句语法描述图
表达式语法描述
图4-9表达式语法描述图
项语法描述
图4-10项语法描述图
因子语法描述
图4-11因子语法描述图
4.5 语义分析与符号表
4.5.1符号表功能
符号表具有功能如下:
- 借助符号表进行上下文相关的静态语义分析,
- 确保符号表可以体现作用域规则,
- 确保标识符属性与上下文环境一致
- 确保标识符先声明后引用,
- 确保标识符的长度、数字的位数、过程嵌套说明的层数符合PL/O语言的约定
- 提示语义错误信息
4.5.2数据结构
符号表的结构如下:
Enum object {
constant,variable,procedur,
};
Struct tablestruct {
char name[al];/*al-名字最大长度*/
enum object kind;
int val; /*constant常量标识符的数值*/
int level; /*标识符所在的层,constant标识符不用*/
int adr; /*标识符相对地址,constant标识符不用*/
int size; /*需分配的数据区大小,仅procedur标识符用到*/
};
Struct tablestruct table[txmax];/*txmax-符号表容量*/
对程序进行自顶向下分析,对于常量值不设置存储空间,对过程和变量记录其对应的层次,在数据段顺序开辟空间,变量size的值等于全局定义的常量和变量数加过程中的局部变脸数。在执行程序时,对符号表自底向上分析,对于重名的变量x,自底向上分析先找的变量x即为局部变量值,这与PL/0程序的结构也有一定关系。主程序没有名字,不像C语言的main函数一样,符号表的第0行实际上存储的是过程名的默认值procedure,size区则是所有的全局变量。
4.6目标代码生成
4.6.1目标代码结构
目标代码采用的类P-code语言是一种栈式机的语言,接近于汇编语言。此类栈式机没有累加器和通用寄存器,有一个栈式存储器,有四个控制寄存器(指令寄存器I,指令地址寄存器P,栈顶寄存器T和基址寄存器B)。
f(操作码) |
l(层次差,标识符引用层减去定义层) |
a(不同的指令含义不同) |
指令格式:
目标代码所采用的数据结构
运行栈int s[stacksize]
指令寄存器:
struct instruction
{
enum fct f; /*虚拟机代码指令*/
int l;/*引用层与声明层的层差*/
int a;/*因不同的f各异*/
};
虚拟机代码段struct instruction code[cxmax];
4.6.2函数调用
目标代码生成主要调用函数statement()处理语句,在statement()函数内部调用position函数查询当前处理的标识符在符号表中的位置,调用gen()函数生成虚拟机代码,最后用listcode()函数打印目标代码。
4.7 错误处理
4.7.1 PL/0编译程序对语法错误
采用短语层恢复方法,跳过一些后面输入的单词符号,直到读入一个能使编译程序恢复正常语法分析工作的单词为止,为了尽可能好的跳转到正确的位置需要开始符号集和后跟符号集。
具体做法是:在进入某个语法单位时,调用TEST函数,检查当前符号是否属于该语法单位的开始符号集合。若不属于,则滤去开始符号和后跟符号集declbegsys、statbegsys外的所有符号在语法单位分析结束时,调用TEST函数,检查当前符号是否属于调用该语法单位时应有的后跟符号集合。若不属于,则滤去后跟符号和开始符号集合外的所有符号。
最好的情况下,编译器正好跳到最近的正确的位置。最坏的情况下,编译器一直跳到错误的位置,这也是为什么程序只有一个错误,但是会显示一连串的错误。
4.7.2 PL/0编译程序对语义错误
如标识符未加说明就引用,或虽经说明,但引用与说明的属性不一致。这时只给出错误信息和出错的位置,编译工作可继续进行
4.7.3 PL/0编译程序对运行错误
如溢出、越界等,只能在运行时发现,由于PL/0编译程序的功能限制无法指出运行时所发生的错误在源程序的对应位置
4.7.4 PL/0语言的出错信息表
分析过程中发现有语法错误,给出提示信息。
表4-1 出错信息表
错误编号 |
错误原因 |
错误编号 |
错误原因 |
错误编号 |
错误原因 |
错误编号 |
错误原因 |
0 |
"程序头格式不正确" |
10 |
"语句之间漏了';'。" |
20 |
"应为关系运算符。 |
30 |
"参数类型不正确,此处应为变量。" |
1 |
"常数说明中的'='写成 ':='。" |
11 |
"标识符未说明。" |
21 |
"表达式内标识符属性不能是过程。 |
31 |
"数越界" |
2 |
"常数说明中的'='后应是数字。" |
12 |
"赋值号左侧标识符有误,应该为变量" |
22 |
"表达式中漏掉右括号')'。" |
32 |
"层次嵌套太深。" |
3 |
"常数声明中的标识符后应是等号'='。" |
13 |
"赋值语句左部变量后应是赋值号” |
23 |
"因子后的非法符号。" |
33 |
"缺少右括号')'" |
4 |
"const,var,procedurc 定义应为变量标识符。" |
14 |
"call后应为标识符。 " |
24 |
"表达式地开始符不能是此号。" |
34 |
"缺少左括号'('" |
5 |
"漏掉了','或';'。" |
15 |
"call 后标识符属性应为过程" |
25 |
"FOR 语句格式不正确" |
35 |
"write 语句括号中的 标识符不是变量。" |
6 |
"过程说明后单词有误,须为procedurc 或者 begin。" |
16 |
"if 判断语句却是 then。" |
26 |
"FOR 语句中丢了'do'" |
36 |
"数组容量不能为0" |
7 |
"应是语句开始符。" |
17 |
"丢了'end'或';'。" |
27 |
"标识符重复定义" |
37 |
"缺少‘]’" |
8 |
"程序体内语句部分的后跟符不正确。" |
18 |
"while 型循环语句中丢了'do'。" |
28 |
"输入错误" |
38 |
"缺少until" |
9 |
"程序结尾丢了句号 '.'。" |
19 |
"语句后的符号不正确" |
29 |
"输入需要为整数" |
4.7.5 函数调用
(1)error()函数
打印出错位置和错误编码。
(2)test ()函数
采用短语层恢复思想测试当前符号是否合法在某一部分(如一条语句,一个表达式)将要结束时,检测下一个符号属于该部分的后跟符号集合和补救用的集合,检测不通过时报错错误号为n。
4.8 存储分配
存储区只需以数组CODE存放的只读目标程序和运行时的数据区S,S是由解释程序定义的一维整型数组。由于PL/0语言的目标程序是一种假想的栈式计算机的汇编语言,现仍用Pascal语言解释执行,解释执行时的数据空间S为栈式计算机的存储空间。遵循后进先出规则,对每个过程(包括主程序)当被调用时,才分配数据空间,退出过程时,所分配的数据空间被释放。
在每个过程调用时在栈顶分配3个联系单元:
(1)SL静态链,指向定义该过程的直接外过程(或主程序)运行时最新数据段的基地址。
(2)DL:动态链,指向调用该过程前正在运行过程的数据段基地址。
(3)RA:返回地址,记录调用该过程时目标程序的断点,即调用过程指令的下一条指令的地址。
5功能扩展算法描述
在本次改进中,通过对PL/0程序的头文件,源文件的更新以及定义新的头文件,小组共实现15个功能,包括一维数组,++,--,+=,-=,*=,/=,%(取余),!(取反),repeat,for,else,处理注释,错误提示,标识符或变量中可以有下划线。添加了12个保留字,添加了两条opr指令。在原有的语法基础上,更新了EBNF。错误处理上,新增了对数组,repeat语句等的错误处理。
5.1 扩展一维数组
EBNF表示:<数组>:=<标识符>[<数字>]
5.1.1 加入定义
在enum symbol{}符号枚举集中加入arrayp,并将symnum+1。在头文件中加入sum作为全局变量记录数组长度。
5.1.2 修改getsym()函数
5.1.3 修改block()函数
5.1.4 修改enter()函数
5.2 增加++,--功能
EBNF表示:<加法运算符> ::= +|-|++|--
5.2.1 在初始字符集中加入定义
在enum symbol{}符号枚举集中加入inc,dec,并将symnum+2。
5.2.2 修改getsym()函数
自加:
自减:
5.2.3 修改statement()函数
前自加:
后自加:
后自减:
前自减:
5.2.4 修改factor()函数
5.3 增加+=,-=,*=,/=功能
EBNF表示:<赋值语句> ::= <标识符>+=|-=|*=|/=<标识符>
5.3.1 在初始字符集中加入定义
在enum symbol{}符号枚举集中加入pluseq,minueq,timeseq,slasheq,并将symnum+4。
5.3.2 修改getsym()函数
+=
-=
*=
/=
5.3.3 修改statement()函数
*=
/=
-=
+=
5.4 扩展%mod取余
EBNF表示:<乘法运算符> ::= * | /|%
5.4.1 在符号集中加入定义
在enum symbol{}符号枚举集中加入mod,并将symnum+1。
5.4.2 修改init()函数
ssym['%']=mod;
5.4.3 修改getsym()函
5.4.4修改term()函
5.4.5 修改interpret()函数
5.5扩展!取反
EBNF表示:<表达式> ::= [+|-|!]<项>
5.5.1 在符号集中加入定义
在enum symbol{}符号枚举集中加入notsym,并将symnum+1。
5.5.2 修改init()函数
ssym['!']=notsym;
5.5.3 修改getsym()函数
5.5.4 修改expression()函数
5.5.5 修改interpret()函数
5.6 增加repeat,for,else
5.6.1 在符号集中加入定义
在enum symbol{}符号枚举集中加入repeatsym,untilsym,forsym,tosym,elsesym,并将symnum+5。
5.6.2 在初始化中将该保留字加入字符集中
5.6.3 修改statement()函数
repeat until保留字:
EBNF表示:<循环语句>::=repeat<语句>until<条件>
for to保留字:
EBNF表示:<循环语句>::=for<赋值语句>to<integer>do<语句>
else保留字:
EBNF表示:<条件语句>::=if<条件>then<语句>[else<语句>]
5.7 处理注释
EBNF表示:<注释>:=’/”*’<语句>‘*’’/’
5.7.1 修改getsym()函数
5.8 错误提示
将所有错误原因规整到errmsg.h文件中,添加头文件定义,定义各个错误的具体原因,更改error函数,使其输出详细的错误信息
5.9 标识符或变量中可以有下划线
EBNF表示:<标识符> ::= <字母><_>{<字母>|<数字><_>}
5.9.1 getsym()函数
6调试运行
6.1 测试一维数组
测试用例:创建一个一维数组并对该数组的第一个元素赋值。
const b_1=10;
var a[4];
begin 执行结果:
a[1]:=b_1;
write(a[1]);
end.
6.2 测试++,--
测试用例:a的初始值为5,测试前++,后++,前--,后-- 的运算
var a,b,c,d,e;
begin
a:=5;
b:=a++;
write(b);
a:=5; 执行结果:
c:=++a;
write(c);
a:=5;
d:=a--;
write(d);
a:=5;
e:=--a;
write(e);
end.
6.3 测试+=,-=,*=,/=
测试用例:a的初始值为10,依次对a进行+=2,-=2,*=2,/=2的运算并输出各个运算的结果
var a;
begin
a:=10;
a+=2;
write(a); 执行结果:
a-=2;
write(a);
a*=2;
write(a);
a/=2;
write(a);
end.
6.4测试repeat,until
测试用例:利用repeat计算1+2+3+······+100的值,输出求和的结果以及执行结束时a的值。
var a,sum;
begin
sum:=0;
a:=1; 执行结果:
repeat
sum+=a;
a++;
until a>100;
write(sum);
write(a);
end.
6.5 测试for
测试用例:利用for计算1+2+3+······+b的值并输出(b的值由输入获得)
var sum,a,b;
procedure p;
begin
sum:=0;
for a:=1 to b do 执行结果:
sum+=a;
end;
begin
read(b);
while b<>0 do
begin
call p;
write(sum);
read(b);
end
end.
6.6 测试else
测试用例:输入a,b,c三个变量的值,利用if else结构进行比较,输出三个变量中的最大值。
var a,b,c;
begin
read(a,b,c);
if a>b then
begin 执行结果:
if a>c then
write(a)
else
write(c);
end
else
begin
if b>c then
write(b)
else
write(c);
end;
end.
6.7测试取余%,取反!
测试用例:12对5取余得到2,在C语言的取反中把大于等于1的数统看作真(1),所以取反为假(0)。
var a,b;
begin
a:=5; 执行结果:
b:=12;
b:=b%a;
write(b);
a:=!b;
write(a);、:
a:=!a;
write(a);
end.
6.8 测试标识符和变量的下划线,处理注释,错误处理
测试用例:定义一个带下划线的常量,添加注释,缺少右方括号
const b_1=10;/*zhushi*/ 执行结果:
var a[4;
begin
a[1]:=b_1;
write(a[1]);
end.
参考文献
- 王生原. 编译原理.第3版[M]. 清华大学出版社, 2015.3-16
- 张瑞红. FOR语句在PL/0编译器中的不同实现[J]. 科技信息(学术版), 2007.
- 高艳玲. 编译原理类C教学语言的研究与实现[D]. 大连海事大学, 2008.
- 徐艳群,张斌.《编译原理》课程设计实践教学改革探索[J].科技展望,2016,26(27):178-179.
PL/0编译程序的研究与改进相关推荐
- PL/0编译程序的简单实现
文章目录 PL-0 代码参考 介绍 PL0编译程序的结构图与解释执行结构 函数功能说明表 过程和函数嵌套定义结构 总体流程图 GetCH() GetSym() BLOCK 语法描述 PL/0文法的EB ...
- 第1关:使用C/C++语言编写PL/0编译程序的词法分析程序
任务描述 使用C/C++语言编写PL/0编译程序的词法分析程序.需要注意的点: (1)识别非法字符:如 @ . & 和 ! 等: (2)识别非法单词:数字开头的数字字母组合: (3)标识符和无 ...
- PL/0语言编译程序分析
PL/0语言是Pascal语言的一个子集,我们这里分析的PL/0的编译程序包括了对PL/0语言源程序进行分析处理.编译生成类PCODE代码,并在虚拟机上解释运行生成的类PCODE代码的功能. PL/0 ...
- 编译原理三级项目PL/0的研究与改进
PL/0的研究与改进 1.扩充else语句 if-then-else语句的EBNF范式描述 <条件语句>:=if<条件>then<语句>[else <语句&g ...
- 编译原理 实验4 语义分析(基于PL/0,使用C++代码编写)
文章目录 1 实验任务 2 实验内容 3 错误类型声明 4 文件结构与代码 4.1 代码结构 4.2 详细代码 4.3 递归下降子程序的声明 5 常变量说明 6 运行结果 1 实验任务 审查每一个语法 ...
- (编译原理)实验四 扩展功能的PL/O编译程序
一. 实验目的 为了更好的配合<编译原理>有关词法分析章节的教学 加深和巩固学生对于语法分析的了解和掌握 让学生进一步的认识PL/0语言的基础和简单的程序编写 使学生通过本实验能够扩大对p ...
- C# 2.0对现有语法的改进
C# 2.0对现有语法的改进 原注:lover_P 出处: [自序] 尽管Microsoft Visual Studio .NET 2005(过去好像叫Visual Studio .NET 2004) ...
- 基于NSGA-II算法的研究和改进
基于NSGA-II算法的研究和改进 在大学的时候学习了有关NSGA2算法的相关知识,对这个颇有兴趣,想着把自己学习的内容都记录下来.同时也是自己第一次开始写博客,想开始自己以后的博客之路,为下班后的业 ...
- PL/0源程序C语言版
//PL0 编译器源代码(pl0.h) /*PL/0 编译系统C 版本头文件 pl0.h*/ # define norw 13 /*关键字个数*/ # define txmax 100 /*名字表容量 ...
最新文章
- ViewPager 的点击事件回调
- java jvm学习笔记二(类装载器的体系结构)
- C# 浅拷贝与深拷贝区别 解惑篇
- Mysql数据备份恢复及主从同步
- ocelot和nginx比较_nginx + ocelot+.net core signalr 关于websocket无法正常握手的问题
- 关于 SAP 产品 UI 的搜索引擎优化 SEO - Search Engine Optimization
- C/C++函数名修饰约定
- python书写风格_python书写风格
- 实现输入提示 layui_ASP.NET Core SignalR :学习消息通讯,实现一个消息通知
- 004 classmates subject
- Spark之性能优化(重点:并行流数据接收)
- 微信深色模式最大的槽点终于被干掉了,这一次安卓用户先享受!
- Python基础第五天
- 【网络基础】《TCP/IP详解》学习笔记6
- r-cnn 行人检测_了解对象检测和R-CNN。
- 含有共轭复数根的Jordan分解
- 双分支定向耦合器 HFSS仿真
- YC出品的创业第一课:How to start a startup
- Drying POJ - 3104 二分
- 成都大数据分析培训转行分享:沉淀自己,静候风来