一个PASCAL语言子集PL0编译器的设计与实现.docx

上传人:b****7 文档编号:9808553 上传时间:2023-02-06 格式:DOCX 页数:29 大小:314.67KB
下载 相关 举报
一个PASCAL语言子集PL0编译器的设计与实现.docx_第1页
第1页 / 共29页
一个PASCAL语言子集PL0编译器的设计与实现.docx_第2页
第2页 / 共29页
一个PASCAL语言子集PL0编译器的设计与实现.docx_第3页
第3页 / 共29页
一个PASCAL语言子集PL0编译器的设计与实现.docx_第4页
第4页 / 共29页
一个PASCAL语言子集PL0编译器的设计与实现.docx_第5页
第5页 / 共29页
点击查看更多>>
下载资源
资源描述

一个PASCAL语言子集PL0编译器的设计与实现.docx

《一个PASCAL语言子集PL0编译器的设计与实现.docx》由会员分享,可在线阅读,更多相关《一个PASCAL语言子集PL0编译器的设计与实现.docx(29页珍藏版)》请在冰豆网上搜索。

一个PASCAL语言子集PL0编译器的设计与实现.docx

一个PASCAL语言子集PL0编译器的设计与实现

 

编译原理

课程设计报告

 

班级:

姓名:

 

2007-12

 

目录

 

1.设计任务

2.原理框图

3.函数说明

4.程序代码

5.程序测试

6.试验体会

7.参考资料

 

一.设计任务

课程设计的教学基本要求

1.巩固和加深对编译原理的理解,提高综合运用本课程所学知识的能力。

2.培养独立思考,深入研究,分析问题、解决问题的能力。

3.能够按要求编写课程设计报告书,能正确阐述设计和实验结果,正确绘制系统和程序框图。

4.通过课程设计,培养严肃认真的工作作风。

●课程设计题目

一个PASCAL语言子集(PL/0)编译器的设计与实现

●PL/0语言的BNF描述(扩充的巴克斯范式表示法)

→program

→[][][{;}]

→const{,}

:

=

→var{,}

→procedure[({,})];

→begin{;}end

:

=

|ifthen[else]

|whiledo

|call[({,})]

|

|read({,})

|write({,})

|odd

→[+|-]{}

{}

||(

→=|<>|<|<=|>|>=

→+|-

→*|/

→l{l|d}(注:

l表示字母)

→d{d}

注释:

程序;

块、程序体;

常量说明;

常量;

变量说明;

分程序;:

复合语句;

语句;

表达式;

条件;

项;

因子;

加法运算符;

乘法运算符;

关系运算符。

●假想目标机的代码

LIT0,a取常量a放入数据栈栈顶

OPR0,a执行运算,a表示执行某种运算

LODL,a取变量(相对地址为a,层差为L)放到数据栈的栈顶

STOL,a将数据栈栈顶的内容存入变量(相对地址为a,层次差为L)

CALL,a调用过程(转子指令)(入口地址为a,层次差为L)

INT0,a数据栈栈顶指针增加a

JMP0,a无条件转移到地址为a的指令

JPC0,a条件转移指令,转移到地址为a的指令

REDL,a读数据并存入变量(相对地址为a,层次差为L)

WRT0,0将栈顶内容输出

代码的具体形式:

其中:

F段代表伪操作码

L段代表调用层与说明层的层差值

A段代表位移量(相对地址)

进一步说明:

INT:

为被调用的过程(包括主过程)在运行栈S中开辟数据区,这时A段为所需数据单元个数(包括三个连接数据);L段恒为0。

CAL:

调用过程,这时A段为被调用过程的过程体(过程体之前一条指令)在目标程序区的入口地址。

LIT:

将常量送到运行栈S的栈顶,这时A段为常量值。

LOD:

将变量送到运行栈S的栈顶,这时A段为变量所在说明层中的相对位置。

STO:

将运行栈S的栈顶内容送入某个变量单元中,A段为变量所在说明层中的相对位置。

JMP:

无条件转移,这时A段为转向地址(目标程序)。

JPC:

条件转移,当运行栈S的栈顶的布尔值为假(0)时,则转向A段所指目标程序地址;否则顺序执行。

OPR:

关系或算术运算,A段指明具体运算。

●假想机的结构

两个存储器:

存储器CODE,用来存放P的代码

数据存储器STACK(栈)用来动态分配数据空间

四个寄存器:

一个指令寄存器I:

存放当前要执行的代码

一个栈顶指示器寄存器T:

指向数据栈STACK的栈顶

一个基地址寄存器B:

存放当前运行过程的数据区在STACK中的起始地址

一个程序地址寄存器P:

存放下一条要执行的指令地址

该假想机没有供运算用的寄存器。

所有运算都要在数据栈STACK的栈顶两个单元之间进行,并用运算结果取代原来的两个运算对象而保留在栈顶。

●活动记录:

RA:

返回地址

DL:

调用者的活动记录首地址

SL:

保存该过程直接外层的活动记录首地址

过程返回可以看成是执行一个特殊的OPR运算

注意:

层次差为调用层次与定义层次的差值

程序实现要求

PL/0语言可以看成PASCAL语言的子集,它的编译程序是一个编译解释执行系统。

PL/0的目标程序为假想栈式计算机的汇编语言,与具体计算机无关。

PL/0的编译程序和目标程序的解释执行程序都是用PASCAL语言书写的,因此PL/0语言可在配备PASCAL语言的任何机器上实现。

其编译过程采用一趟扫描方式,以语法分析程序为核心,词法分析和代码生成程序都作为一个独立的过程,当语法分析需要读单词时就调用词法分析程序,而当语法分析正确需要生成相应的目标代码时,则调用代码生成程序。

用表格管理程序建立变量、常量和过程表示符的说明与引用之间的信息联系。

用出错处理程序对词法和语法分析遇到的错误给出在源程序中出错的位置和错位性质。

当源程序编译正确时,PL/0编译程序自动调用解释执行程序,对目标代码进行解释执行,并按用户程序的要求输入数据和输出运行结果。

 

二.原理与框图

(一).PL/0编译程序功能的框架

PL/0的编译程序包括了对PL/0语言源程序进行分析处理、编译生成类PCODE代码,并在虚拟机上解释运行生成的类PCODE代码的功能。

 

(二).PL/0编译程序的总体设计

PL/0语言编译程序采用以语法分析为核心、一遍扫描的编译方法。

词法分析和代码生成作为独立的子程序供语法分析程序调用。

当语法分析需要读单词时就调用词法分析程序,而当语法、语义分析正确,需要生成相应的目标代码时,则调用代码生成程序。

语法分析的同时,提供了出错报告和出错恢复的功能。

在源程序没有错误编译通过的情况下,调用类PCODE解释程序解释执行生成的类PCODE代码。

 

(三)词法分析子程序分析:

SYM:

存放单词的类别

ID:

存放用户所定义的标识符的值

NUM:

存放用户定义的数

  词法分析子程序名为GetSym(),功能是从源程序中读出一个单词符号(token),把它的信息放入全局变量sym、id和num中,语法分析器需要单词时,直接从这三个变量中获得。

getsym过程通过反复调用getch子过程从源程序过获取字符,并把它们拼成单词。

采用循环分支法。

  词法分析器的分析过程:

调用getsym时,它通过getch过程从源程序中获得一个字符。

如果这个字符是字母,则继续获取字符或数字,最终可以拼成一个单词,查保留字表,如果查到为保留字,则把sym变量赋成相应的保留字类型值;如果没有查到,则这个单词应是一个用户自定义的标识符(可能是变量名、常量名或是过程的名字),把sym置为ident,把这个单词存入id变量。

查保留字表时使用了二分法查找以提高效率。

如果getch获得的字符是数字,则继续用getch获取数字,并把它们拼成一个整数,然后把sym置为number,并把拼成的数值放入num变量。

如果识别出其它合法的符号(比如:

赋值号、大于号、小于等于号等),则把sym则成相应的类型。

如果遇到不合法的字符,把sym置成nul。

通过三个全程量SYM、ID和NUM将识别出的单词信息传递给语法分析程序。

*词法分析的流程示意图:

说明:

SYM为一枚举类型,定义如下

enumsymbol{/*枚举类型*/

SYM_program=1,/*1*/

SYM_const=2,/*2*/

SYM_var,/*3*/

SYM_procedure,/*4*/

SYM_odd,/*5*/

SYM_begin,/*6*/

SYM_end,/*7*/

SYM_if,/*8*/

SYM_then,/*9*/

SYM_else,/*10*/

SYM_while,/*11*/

SYM_do,/*12*/

SYM_call,/*13*/

SYM_read,/*14*/

SYM_write,/*15*/

SYM_ident,/*16标识符*/

SYM_number,/*17数字*/

SYM_become,/*18*//*:

=*/

SYM_plus/*19*//*+*/

SYM_minus/*20*//*-*/

SYM_mul,/*21*//***/

SYM_div,/*22*//*/*/

SYM_lpar,/*23*//*(*/

SYM_rpar,/*24*//*)*/

SYM_semicolon/*25*//*;*/

SYM_comma,/*26*//*,*/

SYM_lss,/*27*//*<*/

SYM_leq,/*28*//*<=*/

SYM_gtr,/*29*//*>*/

SYM_geq,/*30*//*>=*/

SYM_equ,/*31*//*=*/

SYM_neq,/*32*//*<>*/

}SYM;

(四).语法分析子程序分析:

  语法分析子程序采用了自顶向下的递归子程序法,语法分析同时也根据程序的语意生成相应的代码,并提供了出错处理的机制。

语法分析主要由分程序分析过程(block)、常量定义分析过程(constdeclaration)、变量定义分析过程(vardeclaration)、语句分析过程(statement)、表达式处理过程(expression)、项处理过程(term)、因子处理过程(factor)和条件处理过程(condition)构成。

这些过程在结构上构成一个嵌套的层次结构。

除此之外,还有出错报告过程(error)、代码生成过程(gen)、测试单词合法性及出错恢复过程(test)、登录名字表过程(enter)、查询名字表函数(position)以及列出类PCODE代码过程(listcode)作过语法分析的辅助过程。

  下面按各语法单元分析PL/0编译程序的运行机制。

(1)主程序:

使用到的产生式:

→program

注:

这里id加入符号表时,类型为proceduer;

(2)分程序处理过程(block):

相应的产生式有:

→[][][]

→const{,}

:

=

→var{,}

→procedure[({,})];{;}

→begin{;}end

语法分析开始后,首先调用分程序处理过程(block)处理分程序。

过程入口参数置为:

0层、符号表位置0、出错恢复单词集合为句号、声明符或语句开始符。

进入block过程后,首先把局部数据段分配指针设为3,准备分配3个单元供运行期存放静态链SL、动态链DL和返回地址RA。

然后用tx0记录下当前符号表位置并产生一条jmp指令,准备跳转到主程序的开始位置,由于当前还没有知到主程序究竟在何处开始,所以jmp的目标暂时填为0,稍后再改。

同时在符号表的当前位置记录下这个jmp指令在代码段中的位置。

首先判断是否遇到了常量声明,如果遇到则开始常量定义,把常量存入符号表。

接下去用同样的方法分析变量声明,变量定义过程中会用dx变量记录下局部数据段分配的空间个数。

然后如果遇到procedure保留字则进行过程声明和定义,声明的方法是把过程的名字和所在的层次记入符号表,过程定义的方法就是通过递归调用block过程,因为每个过程都是一个分程序。

由于这是分程序中的分程序,因此调用block时需把当前的层次号lev加一传递给block过程。

分程序声明部分完成后,即将进入语句的处理,这时的代码分配指针cx的值正好指向语句的开始位置,这个位置正是前面的jmp指令需要跳转到的位置。

于是通过前面记录下来的地址值,把这个jmp指令的跳转位置改成当前cx的位置。

并在符号表中记录下当前的代码段分配地址和局部数据段要分配的大小(dx的值)。

生成一条int指令,分配dx个空间,作为这个分程序段的第一条指令。

下面就调用语句处理过程statement分析语句。

分析完成后,生成操作数为0的opr指令,用于从分程序返回(对于0层的主程序来说,就是程序运行完成,退出)。

分程序处理过程阶段分为以下步骤,按顺序执行:

  1.常量定义过程(condecl):

  通过循环,反复获得标识符和对应的值,存入符号表。

符号表中记录下标识符的名字和它对应的值。

  2.变量定义过程(vardecl):

与常量定义类似,通过循环,反复获得标识符,存入符号表。

符号表中记录下标识符的名字、它所在的层及它在所在层中的偏移地址。

3.过程声明和定义(proc):

把过程的名字和所在的层次记入符号表,过程定义的方法就是通过递归调用block过程,因为每个过程都是一个分程序。

这里主要要考虑过程大小size的确定方法,size的确定需要等到过程里变量等分析完之后才可确定下来。

通过记录偏移地址dx确定size的值。

注意:

此处过程定义时,设为不带参数。

4.过程的复合语句分析。

 

 

 

Body部分:

(3)语句处理过程(statememt):

  语句处理过程是一个嵌套子程序,通过调用表达式处理、项处理、因子处理等过程及递归调用自己来实现对语句的分析。

语句处理过程可以识别的语句包括赋值语句、read语句、write语句、call语句、if语句、while语句。

当遇到begin/end语句时,就递归调用自己来分析。

分析的同时生成相应的类PCODE指令。

  赋值语句的处理:

首先获取赋值号左边的标识符,从符号表中找到它的信息,并确认这个标识符确为变量名。

然后通过调用表达式处理过程算得赋值号右部的表达式的值并生成相应的指令保证这个值放在运行期的数据栈顶。

最后通过前面查到的左部变量的位置信息,生成相应的sto指令,把栈顶值存入指定的变量的空间,实现了赋值操作。

使用的产生式:

:

=

read语句的处理:

使用的产生式:

→read({,})

  确定read语句语法合理的前提下(否则报错),生成相应的RED指令

 

 write语句的处理:

使用的产生式:

→write({,})

  与read语句相似。

在语法正确的前提下,生成指令:

通过循环调用表达式处理过程分析write语句括号中的每一个表达式,生成相应WRT指令。

  call语句的处理:

从符号表中找到call语句右部的标识符,获得其所在层次和偏移地址。

然后生成相应的cal指令。

至于调用子过程所需的保护现场等工作是由类PCODE解释程序在解释执行cal指令时自动完成的。

使用的产生式:

→call[({,})]

  if语句的处理:

按if语句的语法,首先调用逻辑表达式处理过程处理if语句的条件,把相应的真假值放到数据栈顶。

接下去记录下代码段分配位置(即下面生成的jpc指令的位置),然后生成条件转移jpc指令(遇0或遇假转移),转移地址未知暂时填0。

然后调用语句处理过程处理then语句后面的语句或语句块。

then后的语句处理完后,当前代码段分配指针的位置就应该是上面的jpc指令的转移位置。

通过前面记录下的jpc指令的位置,把它的跳转位置改成当前的代码段指针位置。

当存在else语句时,则要将真出口的语句块执行完之后添加一条jmp指令,假出口的语句地址为jmp代码之后的一句,即比原先的跳转地址cx加1.而jmp的跳转地址需要分析完else后的语句得到的当前cx值。

即做到执行完真出口的语句之后程序能够自动跳过条件语句假出口对应的语句块。

使用的产生式:

→ifthen[else]

  begin/end语句的处理:

通过循环遍历begin/end语句块中的每一个语句,通过递归调用语句分析过程分析并生成相应代码。

使用的产生式:

  while语句的处理:

首先用cx1变量记下当前代码段分配位置,作为循环的开始位置。

然后处理while语句中的条件表达式生成相应代码把结果放在数据栈顶,再用cx2变量记下当前位置,生成条件转移指令,转移位置未知,填0。

通过递归调用语句分析过程分析do语句后的语句或语句块并生成相应代码。

最后生成一条无条件跳转指令jmp,跳转到cx1所指位置,并把cx2所指的条件跳转指令的跳转位置改成当前代码段分配位置。

使用的产生式:

→whiledo

  

(4)表达式、项、因子处理:

 使用产生式

表达式→[+|-]{}

{}

因子||(

 

(5) 逻辑表达式的处理:

首先判断是否为一元逻辑表达式:

判奇偶。

如果是,则通过调用表达式处理过程分析计算表达式的值,然后生成判奇指令。

如果不是,则肯定是二元逻辑运算符,通过调用表达式处理过程依次分析运算符左右两部分的值,放在栈顶的两个空间中,然后依不同的逻辑运算符,生成相应的逻辑判断指令,放入代码段。

使用产生式|odd

(五),符号表管理

在声明常量,变量,过程的同时,需要进行符号表的登记操作(enter())

在语句执行的时候,在遇到标识符则要通过对符号表的查询(position())获得该标识符的相关信息,提供给目标代码生成使用。

1.符号表table的定义:

enumobject{constant=1,variable=2,procedur=3};

struct{

charname[ID_MAX_NUM];

enumobjectkind;

intlv;/*层次/值*/

intadr;

intsize;

}table[txmax];

对常量,变量,过程三种不同的类型使用同一种存储空间类型,三者主要以kind相互区别。

符号表:

其中kind为CONSTANT时lv代表value

Kind为VARIABLE时lv代表lev

 

(六),目标代码的生成

1.目标代码最终形式:

LIT0,a取常量a放入数据栈栈顶

OPR0,a执行运算,a表示执行某种运算

LODL,a取变量(相对地址为a,层差为L)放到数据栈的栈顶

STOL,a将数据栈栈顶的内容存入变量(相对地址为a,层次差为L)

CALL,a调用过程(转子指令)(入口地址为a,层次差为L)

INT0,a数据栈栈顶指针增加a

JMP0,a无条件转移到地址为a的指令

JPC0,a条件转移指令,转移到地址为a的指令

REDL,a读数据并存入变量(相对地址为a,层次差为L)

WRT0,0将栈顶内容输出

OPR0,aa不同取值时:

OPR0,0过程调用结束后,返回调用点并退栈

OPR0,1栈顶元素取反

OPR0,2次栈顶与栈顶相加,退两个栈元素,结果值进栈

OPR0,3次栈顶减去栈顶,退两个栈元素,结果值进栈

OPR0,4次栈顶乘以栈顶,退两个栈元素,结果值进栈

OPR0,5次栈顶除以栈顶,退两个栈元素,结果值进栈

OPR0,6栈顶元素的奇偶判断,结果值在栈顶

OPR0,8次栈顶与栈顶是否相等,退两个栈元素,结果值进栈

OPR0,9次栈顶与栈顶是否不等,退两个栈元素,结果值进栈

OPR0,10次栈顶是否小于栈顶,退两个栈元素,结果值进栈

OPR0,11次栈顶是否大于等于栈顶,退两个栈元素,结果值进栈

OPR0,12次栈顶是否大于栈顶,退两个栈元素,结果值进栈

OPR0,13次栈顶是否小于等于栈顶,退两个栈元素,结果值进栈

2.目标代码生成过程:

函数名

功能

gen

产生中间代码

(1)对于语法单元:

表达式(expression)、项(term)以及因子(factor),它们的翻译规则可以用规则

(1)至(7)式表示,其中T(A)表示对符号串A进行翻译:

T(″+″term)=T(term)

(1)

T(″-″term)=T(term)″-″

(2)

T(term1″+″term2)=T(term1)T(term2)″+″(3)

T(term1″-″term2)=T(term1)T(term2)″-″(4)

T(factor1″*″factor2)=T(factor1)T(factor2)″*″(5)

T(factor1″/″factor2)=T(factor1)T(factor2)″/″(6)

T(″(″expression″)″)=T(expression)(7)

有了这些翻译规则,可以使过程将表达式转换为后缀表达式的任务大大简化。

(2)赋值语句的翻译处理

赋值语句的翻译处理规则:

T(ident″∶=″expression)=T(expression)

Gen(Sto,lev-level,adr)

其中,T(expression)完成对表达式expression的翻译,会得到相应的一串代码指令,执行这一串代码指令得到的表达式计算结果最终会存放在栈顶。

命令gen(Sto,lev-level,adr)是将栈顶数据存入到由“lev-level,adr”确定的栈地址单元,即标识符ident的变量

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > PPT模板 > 其它模板

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1