编译原理PL0_综合实验

编译原理PL0_综合实验
编译原理PL0_综合实验

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

PL/0语言编译程序采用以语法分析为核心、一遍扫描的编译方法。词法分析和代码生成作为独立的子程序供语法分析程序调用。语法分析的同时,提供了出错报告和出错恢复的功能。在源程序没有错误编译通过的情况下,调用类PCODE解释程序解释执行生成的类PCODE代码。

词法分析子程序分析:

词法分析子程序名为getsym,功能是从源程序中读出一个单词符号(token),把它的信息放入全局变量sym、id和num中,语法分析器需要单词时,直接从这三个变量中获得。(注意!语法分析器每次用完这三个变量的值就立即调用getsym子程序获取新的单词供下一次使用。而不是在需要新单词时才调用getsym过程。)getsym过程通过反复调用getch子过程从源程序过获取字符,并把它们拼成单词。getch过程中使用了行缓冲区技术以提高程序运行效率。

词法分析器的分析过程:调用getsym时,它通过getch过程从源程序中获得一个字符。如果这个字符是字母,则继续获取字符或数字,最终可以拼成一个单词,查保留字表,如果查到为保留字,则把sym变量赋成相应的保留字类型值;如果没有查到,则这个单词应是一个用户自定义的标识符(可能是变量名、常量名或是过程的名字),把sym置为ident,把这个单词存入id变量。查保留字表时使用了二分法查找以提高效率。如果getch获得的字符是数字,则继续用getch获取数字,并把它们拼成一个整数,然后把sym置为number,并把拼成的数值放入num变量。如果识别出其它合法的符号(比如:赋值号、大于号、小于等于号等),则把sym则成相应的类型。如果遇到不合法的字符,把sym置成nul。

语法分析子程序分析:

语法分析子程序采用了自顶向下的递归子程序法,语法分析同时也根据程序的语意生成相应的代码,并提供了出错处理的机制。语法分析主要由分程序分析过程(block)、常量定义分析过程(constdeclaration)、变量定义分析过程(vardeclaration)、语句分析过程(statement)、表达式处理过程(expression)、项处理过程(term)、因子处理过程(factor)和条件处理过程(condition)构成。这些过程在结构上构成一个嵌套的层次结构。除此之外,还有出错报告过程(error)、代码生成过程(gen)、测试单词合法性及出错恢复过程(test)、登录名字表过程(enter)、查询名字表函数(position)以及列出类PCODE代码过程(listcode)作过语法分析的辅助过程。

由PL/0的语法图可知:一个完整的PL/0程序是由分程序和句号构成的。因此,本编译程序在运行的时候,通过主程序中调用分程序处理过程block来分析分程序部分(分程序分析过程中还可能会递归调用block过程),然后,判断最后读入的符号是否为句号。如果是句号且分程序分析中未出错,则是一个合法的PL/0程序,可以运行生成的代码,否则就说明源PL/0程序是不合法的,输出出错提示即可。

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

分程序处理过程:

语法分析开始后,首先调用分程序处理过程(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层的主程序来说,就是程序运行完成,退出)。

常量定义过程:

通过循环,反复获得标识符和对应的值,存入符号表。符号表中记录下标识符的名字和它对应的值。

变量定义过程:

与常量定义类似,通过循环,反复获得标识符,存入符号表。符号表中记录下标识符的名字、它所在的层及它在所在层中的偏移地址。

语句处理过程:

语句处理过程是一个嵌套子程序,通过调用表达式处理、项处理、因子处理等过程及递归调用自己来实现对语句的分析。语句处理过程可以识别的语句包括赋值语句、read语句、write语句、call语句、if语句、while语句。当遇到begin/end语句时,就递归调用自己来分析。分析的同时生成相应的类PCODE指令。

赋值语句的处理:

首先获取赋值号左边的标识符,从符号表中找到它的信息,并确认这个标识符确为变量名。然后通过调用表达式处理过程算得赋值号右部的表达式的值并生成相应的指令保证这个值放在运行期的数据栈顶。最后通过前面查到的左部变量的位置信息,生成相应的sto指令,把栈顶值存入指定的变量的空间,实现了赋值操作。

read语句的处理:

确定read语句语法合理的前提下(否则报错),生成相应的指令:第一条是16号操作的opr指令,实现从标准输入设备上读一个整数值,放在数据栈顶。第二条是sto指令,把栈顶的值存入read语句括号中的变量所在的单元。

write语句的处理:

与read语句相似。在语法正确的前提下,生成指令:通过循环调用表达式处理过程分析write语句括号中的每一个表达式,生成相应指令保证把表达式的值算出并放到数据栈顶

并生成14号操作的opr指令,输出表达式的值。最后生成15号操作的opr指令输出一个换行。

call语句的处理:

从符号表中找到call语句右部的标识符,获得其所在层次和偏移地址。然后生成相应的cal指令。至于调用子过程所需的保护现场等工作是由类PCODE解释程序在解释执行cal 指令时自动完成的。

if语句的处理:

按if语句的语法,首先调用逻辑表达式处理过程处理if语句的条件,把相应的真假值放到数据栈顶。接下去记录下代码段分配位置(即下面生成的jpc指令的位置),然后生成条件转移jpc指令(遇0或遇假转移),转移地址未知暂时填0。然后调用语句处理过程处理then语句后面的语句或语句块。then后的语句处理完后,当前代码段分配指针的位置就应该是上面的jpc指令的转移位置。通过前面记录下的jpc指令的位置,把它的跳转位置改成当前的代码段指针位置。

begin/end语句的处理:

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

while语句的处理:

首先用cx1变量记下当前代码段分配位置,作为循环的开始位置。然后处理while语句中的条件表达式生成相应代码把结果放在数据栈顶,再用cx2变量记下当前位置,生成条件转移指令,转移位置未知,填0。通过递归调用语句分析过程分析do语句后的语句或语句块并生成相应代码。最后生成一条无条件跳转指令jmp,跳转到cx1所指位置,并把cx2所指的条件跳转指令的跳转位置改成当前代码段分配位置。

表达式、项、因子处理:

根据PL/0语法可知,表达式应该是由正负号或无符号开头、由若干个项以加减号连接而成。而项是由若干个因子以乘除号连接而成,因子则可能是一个标识符或一个数字,或是一个以括号括起来的子表达式。根据这样的结构,构造出相应的过程,递归调用就完成了表达式的处理。把项和因子独立开处理解决了加减号与乘除号的优先级问题。在这几个过程的反复调用中,始终传递fsys变量的值,保证可以在出错的情况下跳过出错的符号,使分析过程得以进行下去。

逻辑表达式的处理:

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

判断单词合法性与出错恢复过程分析:

本过程有三个参数,s1、s2为两个符号集合,n为出错代码。本过程的功能是:测试当前符号(即sym变量中的值)是否在s1集合中,如果不在,就通过调用出错报告过程输出

出错代码n,并放弃当前符号,通过词法分析过程获取一下单词,直到这个单词出现在s1或s2集合中为止。

这个过程在实际使用中很灵活,主要有两个用法:

在进入某个语法单位时,调用本过程,检查当前符号是否属于该语法单位的开始符号集合。若不属于,则滤去开始符号和后继符号集合外的所有符号。

在语法单位分析结束时,调用本过程,检查当前符号是否属于调用该语法单位时应有的后继符号集合。若不属于,则滤去后继符号和开始符号集合外的所有符号。

通过这样的机制,可以在源程序出现错误时,及时跳过出错的部分,保证语法分析可以继续下去。

语法分析过程中调用的其它子过程相对比较简单,请参考源程序的注释。

类PCODE代码解释执行过程分析

这个过程模拟了一台可以运行类PCODE指令的栈式计算机。它拥有一个栈式数据段用于存放运行期数据、拥有一个代码段用于存放类PCODE程序代码。同时还拥用数据段分配指针、指令指针、指令寄存器、局部段基址指针等寄存器。

解释执行类PCODE代码时,数据段存储分配方式如下:

对于源程序的每一个过程(包括主程序),在被调用时,首先在数据段中开辟三个空间,存放静态链SL、动态链DL和返回地址RA。静态链记录了定义该过程的直接外过程(或主程序)运行时最新数据段的基地址。动态链记录调用该过程前正在运行的过程的数据段基址。返回地址记录了调用该过程时程序运行的断点位置。对于主程序来说,SL、DL和RA的值均置为0。静态链的功能是在一个子过程要引用它的直接或间接父过程(这里的父过程是按定义过程时的嵌套情况来定的,而不是按执行时的调用顺序定的)的变量时,可以通过静态链,跳过个数为层差的数据段,找到包含要引用的变量所在的数据段基址,然后通过偏移地址访问它。

在过程返回时,解释程序通过返回地址恢复指令指针的值到调用前的地址,通过当前段基址恢复数据段分配指针,通过动态链恢复局部段基址指针。实现子过程的返回。对于主程序来说,解释程序会遇到返回地址为0的情况,这时就认为程序运行结束。

解释程序过程中的base函数的功能,就是用于沿着静态链,向前查找相差指定层数的局部数据段基址。这在使用sto、lod等访问局部变量的指令中会经常用到。

类PCODE代码解释执行的部分通过循环和简单的case判断不同的指令,做出相应的动作。当遇到主程序中的返回指令时,指令指针会指到0位置,把这样一个条件作为终至循环的条件,保证程序运行可以正常的结束。

program pl0(fa,fa1,fa2); (* PL/0编译程序与代码生成解释运行程序*)

(* PL/0 compiler with code generation *)

label 99; (* 声明出错跳转标记*)

(* 在Turbo Pascal 7.0中已不允许跨过程的GOTO转移,因此后面的GOTO语句均被我去除了,因此这里的label也没有意义了*)

const (* 常量定义*)

norw = 13; (* of reserved words *) (* 保留字的个数*)

txmax = 100; (* length of identifier table *) (* 标识符表的长度(容量)*)

nmax = 14; (* max number of digits in numbers *) (* 数字允许的最长位数*)

al = 10; (* length of identifiers *) (* 标识符最长长度*)

amax = 2047; (* maximum address *) (* 寻址空间*)

levmax = 3; (* max depth of block nesting *) (* 最大允许的块嵌套层数*)

cxmax = 200; (* size of code array *) (* 类PCODE目标代码数组长度(可容纳代码行数)*)

type (* 类型定义*)

symbol = (nul, ident, number, plus, minus, times, slash, oddsym,

eql, neq, lss, leq, gtr, geq, lparen, rparen, comma,

semicolon, period, becomes, beginsym, endsym, ifsym,

thensym, whilesym, writesym, readsym, dosym, callsym,

constsym, varsym, procsym); (* symobl类型标识了不同类型的词汇*)

alfa = packed array[1..al] of char; (* alfa类型用于标识符*)

object1 = (constant, variable, procedur); (* object1为三种标识符的类型*)

(* 原程序在此使用object作为类型名称,在支持面向对象的Turbo Pascal 7.0中编译不能通过*)

(* wirth used the word "procedure" there, whick won't work! *)

(* 上面一行是课本上的程序清单中的注释,说本程序的原作者Wirth在这里用了procedure 这个词作为标识符类型,是不可以的。

事实上Wirth原本在这里用的词是prozedure,是可以的。*)

symset = set of symbol; (* symset是symbol类型的一个集合类型,可用于存放一组symbol *)

fct = (lit, opr, lod, sto, cal, int, jmp, jpc); (* fct类型分别标识类PCODE的各条指令*) instruction = packed record

f: fct; (* function code *)

l: 0..levmax; (* level *)

a: 0..amax; (* displacement addr *)

end; (* 类PCODE指令类型,包含三个字段:指令f、层差l和另一个操作数a *)

(*

lit 0, a load constant a

opr 0, a execute opr a

lod l, a load variable l, a

sto l, a store variable l, a

cal l, a call procedure a at level l

int 0, a increment t-register by a

jmp 0, a jump to a

jpc 0, a jump conditional to a

*)

var (* 全局变量定义*)

fa: text; (* 文本文件fa用于列出源程序*)

fa1, fa2: text; (* 文本文件fa1用于列出类PCODE代码、fa2用于记录解释执行类PCODE 代码的过程*)

listswitch: boolean; (* true set list object code *) (* 如果本变量置true,程序编译后将为列出类PCODE代码,

否则不列出类PCODE代码*)

ch: char; (* last char read *) (* 主要用于词法分析器,存放最近一次从文件中读出的字符*)

sym: symbol; (* last symbol read *) (* 词法分析器输出结果之用,存放最近一次识别出来的token的类型*)

id: alfa; (* last identifier read *) (* 词法分析器输出结果之用,存放最近一次识别出来的标识符的名字*)

num: integer; (* last number read *) (* 词法分析器输出结果之用,存放最近一次识别出来的数字的值*)

cc: integer; (* character count *) (* 行缓冲区指针*)

ll: integer; (* line length *) (* 行缓冲区长度*)

kk: integer; (* 引入此变量是出于程序性能考虑,见getsym过程注释*)

cx: integer; (* code allocation index *) (* 代码分配指针,代码生成模块总在cx所指位置生成新的代码*)

line: array[1..81] of char; (* 行缓冲区,用于从文件读出一行,供词法分析获取单词时之用*)

a: alfa; (* 词法分析器中用于临时存放正在分析的词*)

code: array[0..cxmax] of instruction; (* 生成的类PCODE代码表,存放编译得到的类PCODE代码*)

word: array[1..norw] of alfa; (* 保留字表*)

wsym: array[1..norw] of symbol; (* 保留字表中每一个保留字对应的symbol类型*) ssym: array[' '..'^'] of symbol; (* 一些符号对应的symbol类型表*)

(* wirth uses "array[char]" here *)

mnemonic: array[fct] of packed array[1..5] of char;(* 类PCODE指令助记符表*) declbegsys, statbegsys, facbegsys: symset; (* 声明开始、表达式开始和项开始符号集合*) table: array[0..txmax] of record (* 符号表*)

name: alfa; (* 符号的名字*)

case kind: object1 of (* 符号的类型*)

constant: (* 如果是常量名*)

(val: integer); (* val中放常量的值*)

variable, procedur: (* 如果是变量名或过程名*)

(level, adr, size: integer) (* 存放层差、偏移地址和大小*)

(* "size" lacking in orginal. I think it belons here *)

end;

fin, fout: text; (* fin文本文件用于指向输入的源程序文件,fout程序中没有用到*)

fname: string; (* 存放PL/0源程序文件的文件名*)

(* 我修改的代码:原程序在此处使用alfa类型,无法在Turbo Pascal 7.0中通过,readln 函数的参数不能为alfa型*)

err: integer; (* 出错总次数*)

(* 出错处理过程error *)

(* 参数:n:出错代码*)

procedure error(n: integer);

begin

writeln('****', ' ': cc-1, '!', n:2); (* 在屏幕cc-1位置显示!与出错代码提示,由于cc

是行缓冲区指针,所以!所指位置即为出错位置*)

writeln(fa1, '****', ' ': cc-1, '!', n:2); (* 在文件cc-1位置输出!与出错代码提示*)

err := err + 1 (* 出错总次数加一*)

end (* error *);

(* 词法分析过程getsym *)

procedure getsym;

var

i, j, k: integer;

(* 读取原程序中下一个字符过程getch *)

procedure getch;

begin

if cc = ll then (* 如果行缓冲区指针指向行缓冲区最后一个字符就从文件读一行到行缓冲区*)

begin

if eof(fin) then (* 如果到达文件末尾*)

begin

write('Program incomplete'); (* 出错,退出程序*)

close(fa);

close(fa1);

close(fin);

halt(0);

{goto 99}

(* 我修改的代码,由于Turbo Pascal 7.0中不允许跨过程的goto,就只能用上面的方法退出程序了。*)

end;

ll := 0; (* 行缓冲区长度置0 *)

cc := 0; (* 行缓冲区指针置行首*)

write(cx: 4, ' '); (* 输出cx值,宽度为4 *)

write(fa1, cx: 4, ' '); (* 输出cx值,宽度为4到文件*)

while not eoln(fin) do (* 当未到行末时*)

begin

ll := ll + 1; (* 行缓冲区长度加一*)

read(fin, ch); (* 从文件读入一个字符到ch *)

write(ch); (* 在屏幕输出ch *)

write(fa1, ch); (* 把ch输出到文件*)

line[ll] := ch; (* 把读到的字符存入行缓冲区相应的位置*)

end;

(* 可见,PL/0源程序要求每行的长度都小于81个字符*)

writeln;

ll := ll + 1; (* 行缓冲区长度加一,用于容纳即将读入的回车符CR *)

read(fin, line[ll]);(* 把#13(CR)读入行缓冲区尾部*)

read(fin, ch); (* 我添加的代码。由于PC上文本文件换行是以#13#10(CR+LF)表示的,

所以要把多余的LF从文件读出,这里放在ch变量中是由于ch 变量的

值在下面即将被改变,把这个多余值放在ch中没有问题*) writeln(fa1);

end;

cc := cc + 1; (* 行缓冲区指针加一,指向即将读到的字符*)

ch := line[cc] (* 读出字符,放入全局变量ch *)

end (* getch *);

begin (* getsym *)

while (ch = ' ') or (ch = #13) do (* 我修改的代码:这句原来是用于读一个有效的字符

(跳过读出的字符中多余的空格),但实际上还要跳

过多余的回车*)

getch;

if ch in ['a'..'z'] then (* 如果读出的字符是一个字母,说明是保留字或标识符*)

begin

k := 0; (* 标识符缓冲区指针置0 *)

repeat (* 这个循环用于依次读出源文件中的字符构成标识符*)

if k < al then (* 如果标识符长度没有超过最大标识符长度(如果超过,就取前面一部分,把多余的抛弃) *)

begin

k := k + 1;

a[k] := ch;

end;

getch (* 读下一个字符*)

until not (ch in ['a'..'z','0'..'9']); (* 直到读出的不是字母或数字,由此可知PL/0的标识符构成规则是:

以字母开头,后面跟若干个字母或数字*)

if k >= kk then (* 如果当前获得的标识符长度大于等于kk *)

kk := k (* 令kk为当前标识符长度*)

else

repeat (* 这个循环用于把标识符缓冲后部没有填入相应字母或空格的空间用空格补足*)

a[kk] := ' ';

kk := kk - 1

until kk = k;

(* 在第一次运行这个过程时,kk的值为al,即最大标识符长度,如果读到的标识符长度小于kk,

就把a数组的后部没有字母的空间用空格补足。

这时,kk的值就成为a数组前部非空格字符的个数。以后再运行getsym时,如果读到的标识符长度大于等于kk,

就把kk的值变成当前标识符的长度。

这时就不必在后面填空格了,因为它的后面肯定全是空格。反之如果最近读到的标识符长度小于kk,那就需要从kk位置向前,

把超过当前标识长度的空间填满空格。

以上的这样一个逻辑,完全是出于程序性能的上考虑。其实完全可以简单的把a数组中a[k]元素以后的空间不管三七二十一全填空格。

*)

(* 下面开始二分法查找看读出的标识符是不是保留字之一*)

id := a; (* 最后读出标识符等于a *)

i := 1; (* i指向第一个保留字*)

j := norw; (* j指向最后一个保留字*)

repeat

k := (i + j) div 2; (* k指向中间一个保留字*)

if id <= word[k] then (* 如果当前的标识符小于k所指的保留字*)

j := k - 1; (* 移动j指针*)

if id >= word[k] then (* 如果当前的标识符大于k所指的保留字*)

i := k + 1 (* 移动i指针*)

until i > j; (* 循环直到找完保留字表*)

if i - 1 > j then (* 如果i - 1 > j表明在保留字表中找到相应的项,id中存的是保留字*) sym := wsym[k] (* 找到保留字,把sym置为相应的保留字值*)

else

sym := ident (* 未找到保留字,把sym置为ident类型,表示是标识符*)

end(* 至此读出字符为字母即对保留字或标识符的处理结束*)

else (* 如果读出字符不是字母*)

if ch in ['0'..'9'] then (* 如果读出字符是数字*)

begin (* number *) (* 开始对数字进行处理*)

k := 0; (* 数字位数*)

num := 0; (* 数字置为0 *)

sym := number; (* 置sym为number,表示这一次读到的是数字*)

repeat (* 这个循环依次从源文件中读出字符,组成数字*)

num := 10 * num + (ord(ch) - ord('0')); (* num * 10加上最近读出的字符ASCII减'0'的ASCII得到相应的数值*)

k := k + 1; (* 数字位数加一*)

getch

until not (ch in ['0'..'9']); (* 直到读出的字符不是数字为止*)

if k > nmax then (* 如果组成的数字位数大于最大允许的数字位数*)

error(30) (* 发出30号错*)

end(* 至此对数字的识别处理结束*)

else

if ch = ':' then (* 如果读出的不字母也不是数字而是冒号*)

begin

getch; (* 再读一个字符*)

if ch = '=' then (* 如果读到的是等号,正好可以与冒号构成赋值号*)

begin

sym := becomes; (* sym的类型设为赋值号becomes *)

getch (* 再读出下一个字*)

end

else

sym := nul; (* 如果不是读到等号,那单独的一个冒号就什么也不是*) end(* 以上完成对赋值号的处理*)

else (* 如果读到不是字母也不是数字也不是冒号*)

if ch = '<' then (* 如果读到小于号*)

begin

getch; (* 再读一个字符*)

if ch = '=' then (* 如果读到等号*)

begin

sym := leq; (* 购成一个小于等于号*)

getch (* 读一个字符*)

end

else (* 如果小于号后不是跟的等号*)

sym := lss (* 那就是一个单独的小于号*)

end

else (* 如果读到不是字母也不是数字也不是冒号也不是小于号*)

if ch = '>' then (* 如果读到大于号,处理过程类似于处理小于号*)

begin

getch; (* 再读一个字符*)

if ch = '=' then (* 如果读到等号*)

begin

sym := geq; (* 购成一个大于等于号*)

getch (* 读一个字符*)

end

else (* 如果大于号后不是跟的等号*)

sym := gtr (* 那就是一个单独的大于号*)

end

else(* 如果读到不是字母也不是数字也不是冒号也不是小于号也不是大于号*)

begin (* 那就说明它不是标识符/保留字,也不是复杂的双字节操作符,应该是一个普通的符号*)

sym := ssym[ch]; (* 直接成符号表中查到它的类型,赋给sym *)

getch (* 读下一个字符*)

end

(* 整个if语句判断结束*)

end (* getsym *);

(* 词法分析过程getsym总结:从源文件中读出若干有效字符,组成一个token串,识别它的类型

为保留字/标识符/数字或是其它符号。如果是保留字,把sym置成相应的保留字类型,如果是

标识符,把sym置成ident表示是标识符,于此同时,id变量中存放的即为保留字字符串或标识

符名字。如果是数字,把sym置为number,同时num变量中存放该数字的值。如果是其它的操作符,

则直接把sym置成相应类型。经过本过程后ch变量中存放的是下一个即将被识别的字符*)

(* 目标代码生成过程gen *)

(* 参数:x:要生成的一行代码的助记符*)

(* y, z:代码的两个操作数*)

(* 本过程用于把生成的目标代码写入目标代码数组,供后面的解释器解释执行*) procedure gen(x: fct; y, z: integer);

begin

if cx > cxmax then (* 如果cx>cxmax表示当前生成的代码行号大于允许的最大代码行数*)

begin

write('program too long'); (* 输出"程序太长",退出*)

close(fa);

close(fa1);

close(fin);

halt(0)

{goto 99}

(* 我修改的代码,由于Turbo Pascal 7.0中不允许跨过程的goto,就只能用上面的方法退出程序了。*)

end;

with code[cx] do (* 把代码写入目标代码数组的当前cx所指位置*)

begin

f := x;

l := y;

a := z;

end;

cx := cx + 1 (* 移动cx指针指向下一个空位*)

end (* gen *);

(* 测试当前单词是否合法过程test *)

(* 参数:s1:当语法分析进入或退出某一语法单元时当前单词符合应属于的集合*)

(* s2:在某一出错状态下,可恢复语法分析正常工作的补充单词集合*)

(* n:出错信息编号,当当前符号不属于合法的s1集合时发出的出错信息*) procedure test(s1, s2: symset; n: integer);

begin

if not (sym in s1) then (* 如果当前符号不在s1中*)

begin

error(n); (* 发出n号错误*)

s1 := s1 + s2; (* 把s2集合补充进s1集合*)

while not (sym in s1) do (* 通过循环找到下一个合法的符号,以恢复语法分析工作*) getsym

end

end (* test *);

(* 语法分析过程block *)

(* 参数:lev:这一次语法分析所在的层次*)

(* tx:符号表指针*)

(* fsys:用于出错恢复的单词集合*)

procedure block(lev, tx: integer; fsys: symset);

var

dx: integer; (* data allocation index *) (* 数据段内存分配指针,指向下一个被分配空间在数据段中的偏移位置*)

tx0: integer; (* initial table index *) (* 记录本层开始时符号表位置*)

cx0: integer; (* initial code index *) (* 记录本层开始时代码段分配位置*)

(* 登陆符号表过程enter *)

(* 参数:k:欲登陆到符号表的符号类型*)

procedure enter(k: object1);

begin (* enter object into table *)

tx := tx + 1; (* 符号表指针指向一个新的空位*)

with table[tx] do (* 开始登录*)

begin

name := id; (* name是符号的名字,对于标识符,这里就是标识符的名字*)

kind := k; (* 符号类型,可能是常量、变量或过程名*)

case k of (* 根据不同的类型进行不同的操作*)

constant: (* 如果是常量名*)

begin

if num > amax then (* 在常量的数值大于允许的最大值的情况下*)

begin

error(31); (* 抛出31号错误*)

num := 0; (* 实际登陆的数字以0代替*)

end;

val := num (* 如是合法的数值,就登陆到符号表*)

end;

variable: (* 如果是变量名*)

begin

level := lev; (* 记下它所属的层次号*)

adr := dx; (* 记下它在当前层中的偏移量*)

dx := dx+1; (* 偏移量自增一,为下一次做好准备*)

end;

procedur: (* 如果要登陆的是过程名*)

level := lev (* 记录下这个过程所在层次*)

end

end

end (* enter *);

(* 登录符号过程没有考虑到重复的定义的问题。如果出现重复定义,则以最后一次的定义为准。*)

(* 在符号表中查找指定符号所在位置的函数position *)

(* 参数:id:要找的符号*)

(* 返回值:要找的符号在符号表中的位置,如果找不到就返回0 *)

function position (id: alfa): integer;

var

i: integer;

begin (* find identifier in table *)

table[0].name := id; (* 先把id放入符号表0号位置*)

i := tx; (* 从符号表中当前位置也即最后一个符号开始找*)

while table[i].name <> id do (* 如果当前的符号与要找的不一致*)

i := i - 1; (* 找前面一个*)

position := i (* 返回找到的位置号,如果没找到则一定正好为0 *)

end(* position *);

(* 常量声明处理过程constdeclaration *)

procedure constdeclaration;

begin

if sym = ident then (* 常量声明过程开始遇到的第一个符号必然应为标识符*)

begin

getsym; (* 获取下一个token *)

if sym in [eql, becomes] then (* 如果是等号或赋值号*)

begin

if sym = becomes then (* 如果是赋值号(常量生明中应该是等号) *)

error(1); (* 抛出1号错误*)

(* 这里其实自动进行了错误纠正使编译继续进行,把赋值号当作等号处理*)

getsym; (* 获取下一个token,等号或赋值号后应接上数字*)

if sym = number then (* 如果的确是数字*)

begin

enter(constant); (* 把这个常量登陆到符号表*)

getsym (* 获取下一个token,为后面作准备*)

end

else

error(2) (* 如果等号后接的不是数字,抛出2号错误*)

end

else

error(3) (* 如果常量标识符后接的不是等号或赋值号,抛出3号错误*) end

else

error(4) (* 如果常量声明过程遇到的第一个符号不为标识符,抛出4号错误*)

end(* constdeclaration *);

(* 变量声明过程vardeclaration *)

procedure vardeclaration;

begin

if sym = ident then (* 变量声明过程开始遇到的第一个符号必然应为标识符*)

begin

enter(variable); (* 将标识符登陆到符号表中*)

getsym (* 获取下一个token,为后面作准备*)

end

else

error(4) (* 如果变量声明过程遇到的第一个符号不是标识符,抛出4号错误*)

end(* vardeclaration *);

(* 列出当前一层类PCODE目标代码过程listcode *)

procedure listcode;

var

i: integer;

begin (* list code generated for this block *)

if listswitch then (* 如果用户选择是要列出代码的情况下才列出代码*)

begin

for i := cx0 to cx - 1 do (* 从当前层代码开始位置到当前代码位置-1处,即为本分程序块*)

with code[i] do

begin

writeln(i: 4, mnemonic[f]: 5, l: 3, a: 5); (* 显示出第i行代码的助记符和L与A操作数*)

(* 我修改的代码:原程序此处在输出i时,没有指定占4个字符宽度,不美观也与下面一句不配套。*)

writeln(fa, i: 4, mnemonic[f]: 5, l: 3, a: 5) (* 同时把屏显打印到文件*)

end;

end

end(* listcode *);

(* 语句处理过程statement *)

(* 参数说明:fsys: 如果出错可用来恢复语法分析的符号集合*)

procedure statement(fsys: symset);

var

i, cx1, cx2: integer;

(* 表达式处理过程expression *)

(* 参数说明:fsys: 如果出错可用来恢复语法分析的符号集合*)

procedure expression(fsys: symset);

var

addop: symbol;

(* 项处理过程term *)

(* 参数说明:fsys: 如果出错可用来恢复语法分析的符号集合*)

procedure term(fsys: symset);

var

mulop: symbol;

(* 因子处理过程factor *)

(* 参数说明:fsys: 如果出错可用来恢复语法分析的符号集合*)

procedure factor(fsys: symset);

var

i: integer;

begin

test(facbegsys, fsys, 24); (* 开始因子处理前,先检查当前token是否在facbegsys 集合中。*)

(* 如果不是合法的token,抛24号错误,并通过fsys集恢复使语法处理可以继续进行*)

while sym in facbegsys do (* 循环处理因子*)

begin

if sym = ident then (* 如果遇到的是标识符*)

begin

i := position(id); (* 查符号表,找到当前标识符在符号表中的位置*)

if i = 0 then (* 如果查符号表返回为0,表示没有找到标识符*)

error(11) (* 抛出11号错误*)

else

with table[i] do (* 如果在符号表中找到了当前标识符的位置,开始生成相应代码*)

case kind of

constant: gen(lit, 0, val); (* 如果这个标识符对应的是常量,值为val,生成lit指令,把val放到栈顶*)

variable: gen(lod, lev - level, adr); (* 如果标识符是变量名,生成lod 指令,*)

(* 把位于距离当前层level的层的偏移地址为adr的变量放到栈顶*)

procedur: error(21) (* 如果在因子处理中遇到的标识符是过程名,出错了,抛21号错*)

end;

getsym (* 获取下一token,继续循环处理*)

end

else

if sym = number then (* 如果因子处理时遇到数字*)

begin

if num > amax then (* 如果数字的大小超过允许最大值amax *)

begin

error(31); (* 抛出31号错*)

num := 0 (* 把数字按0值处理*)

end;

gen(lit, 0, num); (* 生成lit指令,把这个数值字面常量放到栈顶*)

getsym (* 获取下一token *)

end

else

if sym = lparen then (* 如果遇到的是左括号*)

begin

getsym; (* 获取一个token *)

expression( [rparen] + fsys ); (* 递归调用expression子程序分析一个子表达式*)

if sym = rparen then (* 子表达式分析完后,应遇到右括号*)

getsym (* 如果的确遇到右括号,读取下一个token *)

else

error(22) (* 否则抛出22号错误*)

end;

test(fsys, facbegsys, 23) (* 一个因子处理完毕,遇到的token应在fsys集合中*)

(* 如果不是,抛23号错,并找到下一个因子的开始,使语法分析可以继续运行下去*)

end

end(* factor *);

begin (* term *)

factor([times, slash] + fsys); (* 每一个项都应该由因子开始,因此调用factor子程序分析因子*)

while sym in [times, slash] do (* 一个因子后应当遇到乘号或除号*)

begin

mulop := sym; (* 保存当前运算符*)

getsym; (* 获取下一个token *)

factor(fsys + [times, slash]); (* 运算符后应是一个因子,故调factor子程序分析因子*)

if mulop = times then (* 如果刚才遇到乘号*)

gen(opr, 0, 4) (* 生成乘法指令*)

else

gen(opr, 0, 5) (* 不是乘号一定是除号,生成除法指令*)

end

end (* term *);

begin (* expression *)

if sym in [plus, minus] then (* 一个表达式可能会由加号或减号开始,表示正负号*) begin

addop := sym; (* 把当前的正号或负号保存起来,以便下面生成相应代码*)

getsym; (* 获取一个token *)

term(fsys + [plus, minus]); (* 正负号后面应该是一个项,调term子程序分析*)

if addop = minus then (* 如果保存下来的符号是负号*)

gen(opr, 0, 1) (* 生成一条1号操作指令:取反运算*)

(* 如果不是负号就是正号,不需生成相应的指令*)

end

else (* 如果不是由正负号开头,就应是一个项开头*)

term(fsys + [plus, minus]); (* 调用term子程序分析项*)

while sym in [plus, minus] do (* 项后应是加运算或减运算*)

begin

addop := sym; (* 把运算符保存下来*)

getsym; (* 获取下一个token,加减运算符后应跟的是一个项*)

term(fsys + [plus, minus]); (* 调term子程序分析项*)

if addop = plus then (* 如果项与项之间的运算符是加号*)

gen(opr, 0, 2) (* 生成2号操作指令:加法*)

else (* 否则是减法*)

gen(opr, 0, 3) (* 生成3号操作指令:减法*)

end

end (* expression *);

(* 条件处理过程condition *)

(* 参数说明:fsys: 如果出错可用来恢复语法分析的符号集合*)

procedure condition(fsys: symset);

var

relop: symbol; (* 用于临时记录token(这里一定是一个二元逻辑运算符)的内容*) begin

if sym = oddsym then (* 如果是odd运算符(一元) *)

begin

getsym; (* 获取下一个token *)

expression(fsys); (* 对odd的表达式进行处理计算*)

gen(opr, 0, 6); (* 生成6号操作指令:奇偶判断运算*)

end

else (* 如果不是odd运算符(那就一定是二元逻辑运算符) *)

begin

expression([eql, neq, lss, leq, gtr, geq] + fsys); (* 对表达式左部进行处理计算*)

if not (sym in [eql, neq, lss, leq, gtr, geq]) then (* 如果token不是逻辑运算符中的一个*)

error(20) (* 抛出20号错误*)

else

begin

relop := sym; (* 记录下当前的逻辑运算符*)

getsym; (* 获取下一个token *)

expression(fsys); (* 对表达式右部进行处理计算*)

case relop of (* 如果刚才的运算符是下面的一种*)

eql: gen(opr, 0, 8); (* 等号:产生8号判等指令*)

neq: gen(opr, 0, 9); (* 不等号:产生9号判不等指令*)

lss: gen(opr, 0, 10); (* 小于号:产生10号判小指令*)

geq: gen(opr, 0, 11); (* 大于等号号:产生11号判不小于指令*)

gtr: gen(opr, 0, 12); (* 大于号:产生12号判大于指令*)

leq: gen(opr, 0, 13); (* 小于等于号:产生13号判不大于指令*)

end

end

end

end (* condition *);

begin (* statement *)

if sym = ident then (* 所谓"语句"可能是赋值语句,以标识符开头*)

begin

i := position(id); (* 在符号表中查到该标识符所在位置*)

if i = 0 then (* 如果没找到*)

error(11) (* 抛出11号错误*)

else

if table[i].kind <> variable then (* 如果在符号表中找到该标识符,但该标识符不是变量名*)

begin

error(12); (* 抛出12号错误*)

i := 0 (* i置0作为错误标志*)

end;

getsym; (* 获得下一个token,正常应为赋值号*)

if sym = becomes then (* 如果的确为赋值号*)

getsym (* 获取下一个token,正常应为一个表达式*)

else

error(13); (* 如果赋值语句的左部标识符号后所接不是赋值号,抛出13号错误*) expression(fsys); (* 处理表达式*)

if i <> 0 then (* 如果不曾出错,i将不为0,i所指为当前语名左部标识符在符号表中的位置*)

with table[i] do

gen(sto, lev - level, adr) (* 产生一行把表达式值写往指定内存的sto目标代码*) end

else

if sym = readsym then (* 如果不是赋值语句,而是遇到了read语句*)

begin

getsym; (* 获得下一token,正常情况下应为左括号*)

if sym <> lparen then (* 如果read语句后跟的不是左括号*)

error(34) (* 抛出34号错误*)

else

repeat (* 循环得到read语句括号中的参数表,依次产生相应的“从键盘读入”目标代码*)

getsym; (* 获得一个token,正常应是一个变量名*)

if sym = ident then (* 如果确为一个标识符*)

(* 这里略有问题,还应判断一下这个标识符是不是变量名,如果是常量名或过程名应出错*)

i := position(id) (* 查符号表,找到它所在位置给i,找不到时i会为0 *)

else

i := 0; (* 不是标识符则有问题,i置0作为出错标志*)

if i = 0 then (* 如果有错误*)

error(35) (* 抛出35号错误*)

else (* 否则生成相应的目标代码*)

with table[i] do

begin

gen(opr, 0, 16); (* 生成16号操作指令:从键盘读入数字*)

gen(sto, lev - level, adr) (* 生成sto指令,把读入的值存入指定变量所在的空间*)

end;

getsym (* 获取下一个token,如果是逗号,则read语还没完,否则应当是右括号*)

until sym <> comma; (* 不断生成代码直到read语句的参数表中的变量遍历完为止,这里遇到不是逗号,应为右括号*)

if sym <> rparen then (* 如果不是我们预想中的右括号*)

begin

error(33); (* 抛出33号错误*)

while not (sym in fsys) do (* 依靠fsys集,找到下一个合法的token,恢复语法分析*)

getsym

end

else

getsym (* 如果read语句正常结束,得到下一个token,一般为分号或end *) end

else

if sym = writesym then (* 如果遇到了write语句*)

begin

getsym; (* 获取下一token,应为左括号*)

if sym = lparen then (* 如确为左括号*)

begin

repeat (* 依次获取括号中的每一个值,进行输出*)

getsym; (* 获得一个token,这里应是一个标识符*)

expression([rparen, comma] + fsys); (* 调用expression过程分析表达式,用于出错恢复的集合中加上右括号和逗号*)

gen(opr, 0, 14) (* 生成14号指令:向屏幕输出*)

until sym <> comma; (* 循环直到遇到的不再是逗号,这时应是右括号*)

if sym <> rparen then (* 如果不是右括号*)

error(33) (* 抛出33号错误*)

else

getsym (* 正常情况下要获取下一个token,为后面准备好*)

end;

gen(opr, 0, 15) (* 生成一个15号操作的目标代码,功能是输出一个换行*)

(* 由此可知PL/0中的write语句与Pascal中的writeln语句类似,是带有输出换行的*)

end

else

if sym = callsym then (* 如果是call语句*)

begin

getsym; (* 获取token,应是过程名型标识符*)

if sym <> ident then (* 如果call后跟的不是标识符*)

error(14) (* 抛出14号错误*)

else

begin

i := position(id); (* 从符号表中找出相应的标识符*)

if i = 0 then (* 如果没找到*)

error(11) (* 抛出11号错误*)

else

with table[i] do (* 如果找到标识符位于符号表第i位置*)

if kind = procedur then (* 如果这个标识符为一个过程名*)

gen(cal,lev-level,adr) (* 生成cal目标代码,呼叫这个过程*)

else

error(15); (* 如果call后跟的不是过程名,抛出15号错误*)

getsym (* 获取下一token,为后面作准备*)

end

end

else

if sym = ifsym then (* 如果是if语句*)

begin

getsym; (* 获取一token应是一个逻辑表达式*)

condition([thensym, dosym] + fsys); (* 对逻辑表达式进行分析计算,出错恢复集中加入then和do语句*)

if sym = thensym then (* 表达式后应遇到then语句*)

getsym (* 获取then后的token,应是一语句*)

else

error(16); (* 如果if后没有then,抛出16号错误*)

cx1 := cx; (* 记下当前代码分配指针位置*)

gen(jpc, 0, 0); (* 生成条件跳转指令,跳转位置暂时填0,分析完语句后再填写*)

statement(fsys); (* 分析then后的语句*)

code[cx1].a:=cx (* 上一行指令(cx1所指的)的跳转位置应为当前cx所指位置*)

end

else

if sym = beginsym then (* 如果遇到begin *)

begin

getsym; (* 获取下一个token *)

statement([semicolon, endsym] + fsys);(* 对begin与end之间的语句进行分析处理*)

while sym in [semicolon] + statbegsys do (* 如果分析完一句后遇到分号或语句开始符循环分析下一句语句*)

begin

if sym = semicolon then (* 如果语句是分号(可能是空语句)*)

getsym (* 获取下一token继续分析*)

编译原理实验指导

编译原理实验指导 实验安排: 上机实践按小组完成实验任务。每小组三人,分别完成TEST语言的词法分析、语法分析、语义分析和中间代码生成三个题目,语法分析部分可任意选择一种语法分析方法。先各自调试运行,然后每小组将程序连接在一起调试,构成一个相对完整的编译器。 实验报告: 上机结束后提交实验报告,报告内容: 1.小组成员; 2.个人完成的任务; 3.分析及设计的过程; 4.程序的连接; 5.设计中遇到的问题及解决方案; 6.总结。

实验一词法分析 一、实验目的 通过设计编制调试TEST语言的词法分析程序,加深对词法分析原理的理解。并掌握在对程序设计语言源程序进行扫描过程中将其分解为各类单词的词法分析方法。 编制一个读单词过程,从输入的源程序中,识别出各个具有独立意义的单词,即基本字、标识符、常数、运算符、分隔符五大类。并依次输出各个单词的内部编码及单词符号自身值。 二、实验预习提示 1.词法分析器的功能和输出格式 词法分析器的功能是输入源程序,输出单词符号。词法分析器的单词符号常常表示 成以下的二元式(单词种别码,单词符号的属性值)。 2.TEST语言的词法规则 |ID|ID |NUM →a|b|…|z|A|B|…|Z →1|2|…|9|0 →+|-|*|/|=|(|)|{|}|:|,|;|<|>|! →>=|<=|!=|== →/* →*/ 三、实验过程和指导 1.阅读课本有关章节,明确语言的语法,画出状态图和词法分析算法流程图。 2.编制好程序。 3.准备好多组测试数据。 4.程序要求 程序输入/输出示例:

编译原理实验报告二

内蒙古工业大学信息工程学院实验报告 课程名称:编译原理 实验名称:语法制导把表达式翻译成逆波兰式 实验类型:验证性□ 综合性□ 设计性□ 实验室名称: 班级:学号 姓名:组别: 同组人:成绩: 实验日期:

一、实验目的 通过上机实习加深对语法指导翻译原理的理解,掌握运算符优先权的算法,将语法分析所识别的表达式变换成中间代码的翻译方法。 二、实验题目 语法制导把表达式翻译成逆波兰式 三、要求及提示 1、从左到右扫描中缀表达式,经语法分析找出中缀表达式出现的错误并给出错误的具体位置和类型。 2、设一个运算符栈存放暂时不能出现的运算符,逆波兰区存放逆波兰表达式。 3、测试所编程序,给出正确和错误的结果。 4、工具:C语言或其它高级语言 5、实验时间:4学时

实验二语法制导把表达式翻译成逆波兰式 一、实验名称 语法制导把表达式翻译成逆波兰式 二、实验目的 通过上机实习加深对语法指导翻译原理的理解,进一步掌握语法制导翻译的概念,掌握运算符优先权的算法,将语法分析所识别的表达式变换成中间代码的翻译方法。 三、表达式生成逆波兰式的算法 1、初始化△送到运算符栈。 2、扫描左括号“(”,把△送到运算符栈。 3、扫描到变量,把它送到逆波兰区。 4、扫描到运算符 (1)栈内运算符比较 a.栈内运算符>=栈外运算符,把栈内运算符送到逆波兰区。 b.栈内运算符<栈外运算符,把栈外运算符入栈。 ( 2 ) 栈内是△把运算符入栈。 5、扫描右括号“)”。 ( 1 )栈内是运算符,把栈内运算符送到逆波兰区。 ( 2 )栈内是△则△退栈,读入下一个字符。 6、扫描到#(结束符) ( 1 )栈内是运算符,把栈内运算符送到逆波兰区。 ( 2 )栈内是△结束,否则继续分析。 四、程序清单 #include #include int main(){ char str[100]; char exp[100]; char stack[100]; char ch; int flag=1;

编译原理实验报告实验一编写词法分析程序

编译原理实验报告实验名称:实验一编写词法分析程序 实验类型:验证型实验 指导教师:何中胜 专业班级:13软件四 姓名:丁越 学号: 电子邮箱: 实验地点:秋白楼B720 实验成绩: 日期:2016年3 月18 日

一、实验目的 通过设计、调试词法分析程序,实现从源程序中分出各种单词的方法;熟悉词法分析 程序所用的工具自动机,进一步理解自动机理论。掌握文法转换成自动机的技术及有穷自动机实现的方法。确定词法分析器的输出形式及标识符与关键字的区分方法。加深对课堂教学的理解;提高词法分析方法的实践能力。通过本实验,应达到以下目标: 1、掌握从源程序文件中读取有效字符的方法和产生源程序的内部表示文件的方法。 2、掌握词法分析的实现方法。 3、上机调试编出的词法分析程序。 二、实验过程 以编写PASCAL子集的词法分析程序为例 1.理论部分 (1)主程序设计考虑 主程序的说明部分为各种表格和变量安排空间。 数组 k为关键字表,每个数组元素存放一个关键字。采用定长的方式,较短的关键字 后面补空格。 P数组存放分界符。为了简单起见,分界符、算术运算符和关系运算符都放在 p表中 (编程时,还应建立算术运算符表和关系运算符表,并且各有类号),合并成一类。 id和ci数组分别存放标识符和常数。 instring数组为输入源程序的单词缓存。 outtoken记录为输出内部表示缓存。 还有一些为造表填表设置的变量。 主程序开始后,先以人工方式输入关键字,造 k表;再输入分界符等造p表。 主程序的工作部分设计成便于调试的循环结构。每个循环处理一个单词;接收键盘上 送来的一个单词;调用词法分析过程;输出每个单词的内部码。 ⑵词法分析过程考虑 将词法分析程序设计成独立一遍扫描源程序的结构。其流程图见图1-1。 图1-1 该过程取名为 lexical,它根据输入单词的第一个字符(有时还需读第二个字符),判断单词类,产生类号:以字符 k表示关键字;i表示标识符;c表示常数;p表示分界符;s表示运算符(编程时类号分别为 1,2,3,4,5)。 对于标识符和常数,需分别与标识符表和常数表中已登记的元素相比较,如表中已有 该元素,则记录其在表中的位置,如未出现过,将标识符按顺序填入数组id中,将常数 变为二进制形式存入数组中 ci中,并记录其在表中的位置。 lexical过程中嵌有两个小过程:一个名为getchar,其功能为从instring中按顺序取出一个字符,并将其指针pint加1;另一个名为error,当出现错误时,调用这个过程, 输出错误编号。 2.实践部分

编译原理综合性实验报告-分析中间代码生成程序分析

编译原理综合性实验报告-分析中间代码生成程序分析XXXXXX计算机系综合性实验 实验报告 课程名称编译原理实验学期 XXXX 至 XXXX 学年第 X 学期学生所在系部计算机系年级 X 专业班级 XXXXXX 学生姓名 XXX 学号 XXXXXXXXXXXX 任课教师XXX 实验成绩 计算机系制 《编译原理》课程综合性实验报告 开课实验室: 年月日实验题目分析中间代码生成程序 一、实验目的 分析PL/0编译程序的总体结构、代码生成的方法和过程;具体写出一条语句的中间代码生成过程。 二、设备与环境 PC兼容机、Windows操作系统、Turbo Pascal软件等。 三、实验内容 1. 分析PL/0程序的Block子程序,理清PL/0程序结构和语句格式。画出Block 子程序的流程图,写出至少两条PL/0程序语句的语法格式。 2. 分析PL/0程序的Block子程序和Gen子程序,了解代码生成的方法和过程。 使用概要算法来描述语句的代码生成过程。 3. 自己编写一个简单的PL/0程序,能够正确通过编译,得到中间代码。列出自

己编写的源程序和编译后得到的中间代码。 4. 从中选择一个语句或表达式,写出代码生成的过程。要求从自己的源程序中 选择一条语句,结合这条语句写出语义分析和代码生成过程。在描述这个过程中,要说清楚每个功能有哪个子程序的哪条语句来完成,说清楚语句和参数的含义和功能。 四、实验结果及分析 (一)Block子程序分析 1.常量声明的分析: 常量声明部分的语法结构定义为如下形式: -> const ; -> [;] ->id = C 其中C可以是常量标识符或字符串或整数(可带符号)或实数(可带符号)。 常量声明分析程序的主要任务是: (1).扫描整个常量声明部分。 (2).为被声明的常量标识符建立符号表项。 (3).检查重复的声明。 2.变量声明部分的分析: 变量声明部分的语法结构定义为如下形式: -> var -> [;] ->:T ->id[,]

编译原理实验报告

院系:计算机科学学院 专业、年级: 07计科2大班 课程名称:编译原理 学号姓名: 指导教师: 2010 年11月17 日 组员学号姓名

实验 名称 实验一:词法分析实验室9205 实验目的或要求 通过设计一个具体的词法分析程序,加深对词法分析原理的理解。并掌握在对程序设计语言源程序进行扫描过程中将其分解为各类单词的词法分析方法。 编制一个读单词过程,从输入的源程序中,识别出各个具有独立意义的单词,即基本保留字、标识符、常数、运算符、分隔符五大类。并依次输出各个单词的内部编码及单词符号自身值。 具体要求:输入为某语言源代码,达到以下功能: 程序输入/输出示例:如源程序为C语言。输入如下一段: main() { int a,b; a=10; b=a+20; } 要求输出如下(并以文件形式输出或以界面的形式输出以下结果)。 (2,”main”) (5,”(“) (5,”)“) (5,”{“} (1,”int”) (2,”a”) (5,”,”) (2,”b”) (5,”;”) (2,”a”) (4,”=”) (3,”10”) (5,”;”) (2,”b”) (4,”=”) (2,”a”) (4,”+”) (3,”20”) (5,”;”) (5,”}“) 要求: 识别保留字:if、int、for、while、do、return、break、continue等等,单词种别码为1。 其他的标识符,单词种别码为2。常数为无符号数,单词种别码为3。 运算符包括:+、-、*、/、=、>、<等;可以考虑更复杂情况>=、<=、!= ;单词种别码为4。分隔符包括:“,”“;”“(”“)”“{”“}”等等,单词种别码为5。

编译原理实验报告

编译原理实验报告 班级 姓名: 学号: 自我评定:

实验一词法分析程序实现 一、实验目的与要求 通过编写和调试一个词法分析程序,掌握在对程序设计语言的源程序进行扫描的过程中,将字符形式的源程序流转化为一个由各类单词符号组成的流的词法分析方法。 二、实验内容 根据教学要求并结合学生自己的兴趣和具体情况,从具有代表性的高级程序设计语言的各类典型单词中,选取一个适当大小的子集。例如,可以完成无符号常数这一类典型单词的识别后,再完成一个尽可能兼顾到各种常数、关键字、标识符和各种运算符的扫描器的设计和实现。 输入:由符合或不符合所规定的单词类别结构的各类单词组成的源程序。 输出:把单词的字符形式的表示翻译成编译器的内部表示,即确定单词串的输出形式。例如,所输出的每一单词均按形如(CLASS,VALUE)的二元式编码。对于变量和常数,CLASS字段为相应的类别码;VALUE字段则是该标识符、常数的具体值或在其符号表中登记项的序号(要求在变量名表登记项中存放该标识符的字符串;常数表登记项中则存放该常数的二进制形式)。对于关键字和运算符,采用一词一类的编码形式;由于采用一词一类的编码方式,所以仅需在二元式的CLASS字段上放置相应的单词的类别码,VALUE字段则为“空”。另外,为便于查看由词法分析程序所输出的单词串,要求在CLASS字段上放置单词类别的助记符。 三、实现方法与环境 词法分析是编译程序的第一个处理阶段,可以通过两种途径来构造词法分析程序。其一是根据对语言中各类单词的某种描述或定义(如BNF),用手工的方式(例如可用C语言)构造词法分析程序。一般地,可以根据文法或状态转换图构造相应的状态矩阵,该状态矩阵同控制程序便组成了编译器的词法分析程序;也可以根据文法或状态转换图直接编写词法分析程序。构造词法分析程序的另外一种途径是所谓的词法分析程序的自动生成,即首先用正规式对语言中的各类单词符号进行词型描述,并分别指出在识别单词时,词法分析程序所应进行的语义处理工作,然后由一个所谓词法分析程序的构造程序对上述信息进行加工。如美国BELL实验室研制的LEX就是一个被广泛使用的词法分析程序的自动生成工具。 总的来说,开发一种新语言时,由于它的单词符号在不停地修改,采用LEX等工具生成的词法分析程序比较易于修改和维护。一旦一种语言确定了,则采用手工编写词法分析程序效率更高。 四、实验设计 1)题目1:试用手工编码方式构造识别以下给定单词的某一语言的词法分析程序。 语言中具有的单词包括五个有代表性的关键字begin、end、if、then、else;标识符;整型常数;六种关系运算符;一个赋值符和四个算术运算符。参考实现方法简述如下。 单词的分类:构造上述语言中的各类单词符号及其分类码表。 表I 语言中的各类单词符号及其分类码表 单词符号类别编码类别码的助记符单词值

《编译原理》实验指导书

《编译原理》实验指导书 实验目的和内容 编译原理实验的目的是使学生将编译理论运用到实际当中,实现一个简单语言集的词法、语法和语义分析程序,验证实际编译系统的实现方法,并加深对编译技术的认识。 实验内容共需实现编译器的词法、语法和语义分析程序三个组成部分。要求学生必须完成每个实验的基本题目要求,有余力的同学可尝试实验的扩展要求部分。 实验报告 要求每人针对所完成的实验内容上交一份实验报告,其中主要包括三方面内容:1、实验设计:实验采用的实现方法和依据(如描述语言的文法及其机内表示,词分析 的单词分类码表、状态转换图或状态矩阵等,语法分析中用到的分析表或优先矩阵等,语法制导翻译中文法的拆分和语义动作的设计编写等);具体的设计结果(应包括整体设计思想和实现算法,程序结构的描述,各部分主要功能的说明,法以及所用数据结构的介绍等)。 2、程序代码:实验实现的源程序清单,要求符合一般的程序书写风格,有详细的注释。 3、实验结果分析:自行编写若干源程序作为测试用例,对所生成的编译程序进行测试 (编译程序的输入与输出以文件的形式给出);运行结果分析(至少包括一个正确和一个错误单词或语句的运行结果);以及改进设想等。 注意事项 1、电子版实验报告和源程序在最后一次机时后的一周内上交。(每个同学上交一个压 缩文件,其命名格式为“学号_姓名.rar”,内含实验报告和一个命名为“源程序” 的文件夹。注意提交的源程序应是经过调试、测试成功的较为通用的程序,并应有相应的注释、运行环境和使用方法简介。) 2、不接受不完整的实验报告和没有说明注释的源程序,或者说明与程序、运行结果不 符合的作业。 特别鼓励:扩展题目 1、为亲身经历一个小型编译器的开发全过程,触摸一下与实际编译器开发相关的工作, 大家可以自由组成3人左右的小组,推举组长,模拟一个团队分工协作开发大型软件的实战环境,融入软件工程的思想规范和一般理论方法,初步体验从系统分析设计、编码测试到交付维护的一个完整编译器软件的开发过程。要求组长为每个小组成员分配主要负责的任务,完成相应的分析设计员、程序员和测试员等角色的工作,并以小组为单位提交一份实验报告和源程序,在报告封面上写明每个同学主要完成和负责的部分。 2、以组为单位完成的实验内容至少必须整合词法、语法和语义三个部分的实验,对于 选定的适当规模的文法(如C语言的一个大小适宜的子集),进行系统的总体设计、功能分析、编码测试等工作。完成一个从对源程序的词法分析开始,到中间代码生成的完整的编译器前端的开发,使所涉及到的编译系统的各个组成模块有机地衔接在一起,提交一份完整的实验报告和源程序,并将以下几个方面描述清楚:

编译原理实验报告一

实验一词法分析程序实现 一、实验目得与要求 通过编写与调试一个词法分析程序,掌握在对程序设计语言得源程序进行扫描得过程中,将字符流形式得源程序转化为一个由各类单词符号组成得流得词法分析方法 二、实验内容 基本实验题目:若某一程序设计语言中得单词包括五个关键字begin、end、if、then、else;标识符;无符号常数;六种关系运算符;一个赋值符与四个算术运算符,试构造能识别这些单词得词法分析程序(各类单词得分类码参见表I)。 表I语言中得各类单词符号及其分类码表 输入:由符合与不符合所规定得单词类别结构得各类单词组成得源程序文件。 输出:把所识别出得每一单词均按形如(CLASS,VALUE)得二元式形式输出,并将结果放到某个文件中。对于标识符与无符号常数,CLASS字段为相应得类别码得助记符;V AL UE字段则就是该标识符、常数得具体值;对于关键字与运算符,采用一词一类得编码形式,仅需在二元式得CLASS字段上放置相应单词得类别码得助记符,V ALUE字段则为“空". 三、实现方法与环境 词法分析就是编译程序得第一个处理阶段,可以通过两种途径来构造词法分析程序.其一就是根据对语言中各类单词得某种描述或定义(如BNF),用手工得方式(例如可用C语言)构造词法分析程序。一般地,可以根据文法或状态转换图构造相应得状态矩阵,该状态矩阵连同控制程序一起便组成了编译器得词法分析程序;也可以根据文法或状态转换图直接编写词法分析程序。构造词法分析程序得另外一种途径就是所谓得词法分析程序得自动生成,即首先用正规式对语言中得各类单词符号进行词型描述,并分别指出在识别单词时,词法分析程

编译原理实验指导书2010

《编译原理》课程实验指导书 课程编号: 课程名称:编译原理/Compiler Principles 实验总学时数: 8 适用专业:计算机科学与技术、软件工程 承担实验室:计算机学院计算机科学系中心实验室、计算机技术系中心实验室 一、实验教学的目的与要求 上机实习是对学生的一种全面综合训练,是与课堂听讲、自学和练习相辅相成的必不可少的一个教学环节。通常,实习题中的问题比平时的练习题要复杂,也更接近实际。编译原理这门课程安排的2次上机实验都属于一种设计类型的实验,每个实验的训练重点在于基本的编译技术和方法,而不强调面面俱到;实验的目的是旨在使学生进一步巩固课堂上所学的理论知识,深化理解和灵活掌握教学内容;培养学生编制算法的能力和编程解决实际问题的动手能力。 要求学生在上机前应认真做好各种准备工作,熟悉机器的操作系统和语言的集成环境,独立完成算法设计和程序代码的编写;上机时应随带有关的编译原理教材或参考书;要学会程序调试与纠错。 每次实验后要交实验报告,实验报告的内容应包括: (1)实验题目、班级、学号、姓名、完成日期; (2)简要的需求分析与概要设计; (3)详细的算法描述; (4)源程序清单; (5)给出软件的测试方法和测试结果; (6)实验的评价、收获与体会。 开发工具: (1)DOS环境下使用Turbo C; (2)Windows环境下使用Visual C++ 。 考核: 实验成绩占编译原理课程结业成绩的10%。 三、单项实验的内容和要求: 要求每个实验保证每个学生一台微机。 实验一(4学时):单词的词法分析程序设计。 (一)目的与要求 1.目的 通过设计、编制、调试一个具体的词法分析程序,加深对词法分析原理的理解,并掌握在对程序设计语言源程序进行扫描过程中将其分解为各类单词的词法分析方法。

编译原理pL0实验报告

一.课程设计要求 基本内容: (1)扩充赋值运算:*= 和/= (2)扩充语句(Pascal的FOR语句): ①FOR <变量>:=<表达式> TO <表达式> DO <语句> ②FOR <变量>:=<表达式> DOWNTO <表达式> DO <语句> 其中,语句①的循环变量的步长为2, 语句②的循环变量的步长为-2。 二.设计思路 在课内实验的基础上,额外增加*=和/=运算符和关键字的语义动作,以下是设计思路: 1. 扩充单词 在头文件pl0.h中的enum symbol中增加关键字forsym, tosym, downtosym, timeseqlsym,slasheqlsym,并修改关键字数#define symnum 46。 /*初始化*/ // ssym['*=']=timeseql; // ssym['/=']=slasheql; /*设置保留字名字,按照字母顺序,便于折半查找*/ strcpy(&(word[14][0]),"to"); /*增加后需要按序排列*/ strcpy(&(word[7][0]),"for");

strcpy(&(word[4][0]),"downto"); strcpy(&(word[3][0]),"do"); /*设置保留字符号*/ wsym[7]=forsym; wsym[14]=tosym; /*语法分析,获取一个符号*/ 在getsym()部分添加: else if(ch=='*'){ /** “*=” **/ getchdo; if(ch=='='){ sym=timeseql; getchdo; printf("check *= success!"); } else sym=times; } else if(ch=='/'){ /* “/=” */ getchdo; if(ch=='='){sym=slasheql; getchdo; printf("check /= success!");

编译原理综合实验题

编译原理综合实验指导书 一、实验任务 设计、编制并调试一个中缀表达转换为后缀表达的实验程序,加深对词法分析、语法分析、语义分析及代码生成的理解。 二、实验内容 1、词法 输入:扩展ASCII码字符集字符。除大小写26英文字母(letter)和数字0-9(digit)以及+ - * / ^ = ; , ( )以外,所有其他字符一律按等同于空格处理,一般用来分隔单词。 输出:识别单词,单词包括关键字、运算符、界符、标识符和整型常数。 (1)关键字:var (2)运算符和界符:+ - * / ^ = ; , ( ) 其中:乘除运算符(*, /)返回具有不同属性值的单词mulop, 加减运算符(+, -)返回具有不同属性值的单词addop。 (3)标识符(id)和整型常数(num): 标识符(id)和整型常数(num)最大长度为8个字符,定义如下。 id = letter (letter | digit)* num = digit digit* 2、语法 根据输入的单词序列,分析是否符合语法规则,如果不符合,应指明位置与理由;如果符合,则执行相应的语义子程序完成语义分析及中缀表达转换为后缀表达的过程。需注意的是,这里给出的是二义文法,从语义上考虑,表达式的计算按先幂次运算(^),再乘除运算(*, /)的最后加减运算(+, - )的优先顺序;括号((, ))用于调整运算先后顺序,既括号内部分先计算;赋值运算(=)最后进行。本实验系统的语法规则是: program → compound compound → declaration assignstatement compound | ε declaration → var identifier_list ; | ε dentifier_list →id, dentifier_list | id assignstatement →id= expression ; | ε expression → expression addop expression | expression mulop expression | expression ^ expression | ( expression ) | id | num 3、语义分析及代码生成 语义分析的主要任务是判断变量是否先定义后使用。代码生成的的主要任务是将赋值语句从中缀表达转换为后缀表达。

编译原理实验报告总结

学年第学期《编译原理》实验报告 学院(系):计算机科学与工程学院 班级:11303070A 学号:11303070*** 姓名:无名氏 指导教师:保密式 时间:2016 年7 月

目录 1.实验目的 (1) 2.实验内容及要求 (1) 3.实验方案设计 (1) 3.1 编译系统原理介绍 (1) 3.1.1 编译程序介绍 (2) 3.1.2 对所写编译程序的源语言的描述 (2) 3.2 词法分析程序的设计 (3) 3.3 语法分析程序设计 (4) 3.4 语义分析和中间代码生成程序的设计 (4) 4. 结果及测试分析 (4) 4.1软件运行环境及限制 (4) 4.2测试数据说明 (5) 4.3运行结果及功能说明 (5) 5.总结及心得体会 (7)

1.实验目的 根据Sample语言或者自定义的某种语言,设计该语言的编译前端。包括词法分析,语法分析、语义分析及中间代码生成部分。 2.实验内容及要求 (1)词法分析器 输入源程序,输出对应的token表,符号表和词法错误信息。按规则拼单词,并转换成二元形式;滤掉空白符,跳过注释、换行符及一些无用的符号;进行行列计数,用于指出出错的行列号,并复制出错部分;列表打印源程序;发现并定位词法错误; (2)语法分析器 输入token串,通过语法分析,寻找其中的语法错误。要求能实现Sample 语言或自定义语言中几种最常见的、基本的语法单位的分析:算术表达式、布尔表达式、赋值语句、if语句、for语句、while语句、do while语句等。 (3)语义分析和中间代码生成 输入token串,进行语义分析,修改符号表,寻找其中的语义错误,并生 成中间代码。要求能实现Sample语言或自定义语言中几种最常见的、基本的语法单位的分析:算术表达式、布尔表达式、赋值语句、if语句、for语句、while 语句、do while语句等。 实验要求:功能相对完善,有输入、输出描述,有测试数据,并介绍不足。3.实验方案设计 3.1 编译系统原理介绍 编译器逐行扫描高级语言程序源程序,编译的过程如下: (1).词法分析 识别关键字、字面量、标识符(变量名、数据名)、运算符、注释行(给人看的,一般不处理)、特殊符号(续行、语句结束、数组)等六类符号,分别归类等待处理。 (2).语法分析 一个语句看作一串记号(Token)流,由语法分析器进行处理。按照语言的文法检查判定是否是合乎语法的句子。如果是合法句子就以内部格式保存,否则报错。直至检查完整个程序。 (3).语义分析 语义分析器对各句子的语法做检查:运算符两边类型是否相兼容;该做哪些类型转换(例如,实数向整数赋值要"取整");控制转移是否到不该去的地方;是

编译原理实验-词法分析器的设计说明

集美大学计算机工程学院实验报告 课程名称:编译原理班级: 指导教师:: 实验项目编号:实验一学号: 实验项目名称:词法分析器的设计实验成绩: 一、实验目的 通过设计编制调试一个具体的词法分析程序,加深对词法分析原理的理解。并掌握在对程序设计语言源程序进行扫描过程中将其分解为各类单词的词法分析方法。 二、实验容 编写一个词法分析器,从输入的源程序(编写的语言为C语言的一个子集)中,识别出各个具有独立意义的单词,即基本保留字、标识符、常数、运算符、分隔符五大类。并依次输出各个单词的部编码及单词符号自身值。(遇到错误时可显示“Error”,然后跳过错误部分继续显示) 三、实验要求 1、词法分析器的功能和输出格式 词法分析器的功能是输入源程序,输出单词符号。词法分析器的单词符 2 别单词的类型,将标识符和常量分别插入到相应的符号表中,增加错误处理等。 3、编程语言不限。

四、实验设计方案 1、数据字典 本实验用到的数据字典如下表所示:

3、实验程序 #include #include #include #include //判断读入的字符是否为字母 bool isLetter(char c){ if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')){ return true; } else return false; } //判断读入的字符是否为数字 bool isDigit(char c){ if(c >='0' && c <= '9'){ return true; } else return false; } //判断是否为关键字 bool isKey(char *string) { if(!strcmp(string,"void") || !strcmp(string,"if")|| !strcmp(string,"for")|| !strcmp(string,"wh ile") || !strcmp(string,"do")|| !strcmp(string,"return")|| !strcmp(stri ng,"break") || !strcmp(string,"main")|| !strcmp(string,"int")|| !strcmp(strin g,"float")|| !strcmp(string,"char") || !strcmp(string,"double")|| !strcmp(string,"String"))

编译原理实验报告(词法分析器语法分析器)

编译原理实验报告

实验一 一、实验名称:词法分析器的设计 二、实验目的:1,词法分析器能够识别简单语言的单词符号 2,识别出并输出简单语言的基本字.标示符.无符号整数.运算符.和界符。 三、实验要求:给出一个简单语言单词符号的种别编码词法分析器 四、实验原理: 1、词法分析程序的算法思想 算法的基本任务是从字符串表示的源程序中识别出具有独立意义的单词符号,其基本思想是根据扫描到单词符号的第一个字符的种类,拼出相应的单词符号。 2、程序流程图 (1 (2)扫描子程序

3

五、实验内容: 1、实验分析 编写程序时,先定义几个全局变量a[]、token[](均为字符串数组),c,s( char型),i,j,k(int型),a[]用来存放输入的字符串,token[]另一个则用来帮助识别单词符号,s用来表示正在分析的字符。字符串输入之后,逐个分析输入字符,判断其是否‘#’,若是表示字符串输入分析完毕,结束分析程序,若否则通过int digit(char c)、int letter(char c)判断其是数字,字符还是算术符,分别为用以判断数字或字符的情况,算术符的判断可以在switch语句中进行,还要通过函数int lookup(char token[])来判断标识符和保留字。 2 实验词法分析器源程序: #include #include #include int i,j,k; char c,s,a[20],token[20]={'0'}; int letter(char s){ if((s>=97)&&(s<=122)) return(1); else return(0); } int digit(char s){ if((s>=48)&&(s<=57)) return(1); else return(0); } void get(){ s=a[i]; i=i+1; } void retract(){ i=i-1; } int lookup(char token[20]){ if(strcmp(token,"while")==0) return(1); else if(strcmp(token,"if")==0) return(2); else if(strcmp(token,"else")==0) return(3); else if(strcmp(token,"switch")==0) return(4); else if(strcmp(token,"case")==0) return(5); else return(0); } void main() { printf("please input string :\n"); i=0; do{i=i+1; scanf("%c",&a[i]);

编译原理实验指导书(图)

编译原理 实 验 指 导 书

前言 编译原理是计算机科学与技术、软件工程等专业的主干课和必修课,由于这门课程相对抽象且内容较复杂,一直是比较难学的一门课程。在编译原理的学习过程中,实验非常重要,只有通过上机实验,才能使学生对比较抽象的课程内容产生一个具体的感性认识。 本书实验环境主要为C环境及一个词法分析器自动生成工具FLEX和一个语法分析器自动生成工具BISON。书中给出的参考源程序也是C源程序,但由于实验者熟悉精通的语言工具不尽相同,因而强求采用统一的编程语言编程是不现实的。实验者在掌握了编译程序各个阶段的功能和原理之后,不难借助使用其他自己熟悉的语言实现相关功能。 实验者在实验过程中应该侧重写出自己在算法分析、设计思路、实现功能或程序代码等方面的特色,写出设计和实现过程中遭遇到的难点和解决办法,可以不拘泥于实验指导给出的参考性设计思路,尽可能在深度和广度上加以拓展。只有这种各具特色的实验报告,才将更有利于体现实验者在创新思维和动手能力上的差异。 通过这些实验,能使学生对这些部份的工作机理有一个详细的了解,达到“知其然,且知其所以然”的目的。并可在C环境下对自动生成工具生成的词法、语法分析器进行编译调试。 由于手工生成词法和语法分析器的工作量太大,在实际中常用自动生成工具来完成之。这些工具中最著名的当属贝尔实验室的词法分析器生成工具LEX和语法分析器生成工具YACC。它们现已成为UNIX的标准应用程序同UNIX一起发行。与此同时GNU推出与LEX完全兼容的FLEX,与YACC完全兼容的BISON。这两个程序都在Internet上以源代码的形式免费发行,所以很容易在其它操作系统下重新编译安装。我们实验采用的就是for dos的FLEX和BISON。本书有关的编译工具及其源程序例子,可到BISON的网站上下载。关于FLEX和BISON的用法简介,参见附录,如需更详细的介绍,请参阅编译工具中帮助文件。

编 译 原 理 实 验 报 告

编译原理实验报告 课程:编译原理 系别:计算机系 班级:11网络 姓名:王佳明 学号:110912049 教师:刘老师 实验小组:第二组 1

实验一熟悉C程序开发环境、进行简单程序的调试 实验目的: 1、初步了解vc++6.0环境; 2、熟悉掌握调试c程序的步骤: 实验内容: 1、输入下列程序,练习Turbo C 程序的编辑、编译、运行。 #include main() { printf(“Programming is fun.\n”); } 2、分析程序,预测其运行结果,并上机检测你的预测。 #include main() { printf(“*\n”); printf(“* * *\n”); printf(“* * * * *\n”); printf(“* * * * * * *\n”); } 3、下面是一个加法程序,程序运行时等待用户从键盘输入两个整数,然后求出它们的和并输出。观察运行结果(程序输出),上机验证该程序。 #include main() { int a,b,c; printf(“Please input a,b:”); scanf(“%d,%d”,&a,&b); c=a+b; printf(“%d+%d=%d\n”,a,b,c); } 2

实验二词法分析器 一、实验目的: 设计、编制、调试一个词法分析子程序-识别单词,加深对词法分析原理的理解。 二、实验要求: 1.对给定的程序通过词法分析器弄够识别一个个单词符号,并以二元式(单词种别码,单词符号的属性值)显示。而本程序则是通过对给定路径的文件的分析后以单词符号和文字提示显示。 2.本程序自行规定: (1)关键字"begin","end","if","then","else","while","write","read", "do", "call","const","char","until","procedure","repeat" (2)运算符:"+","-","*","/","=" (3)界符:"{","}","[","]",";",",",".","(",")",":" (4)其他标记如字符串,表示以字母开头的标识符。 (5)空格、回车、换行符跳过。 在屏幕上显示如下: ( 1 , 无符号整数) ( begin , 关键字) ( if , 关键字) ( +, 运算符) ( ;, 界符) ( a , 普通标识符) 三、使用环境: Windows下的visual c++6.0; 四、调试程序: 1.举例说明文件位置:f:、、11.txt目标程序如下: begin x:=9 if x>0 then x:=x+1; while a:=0 do 3

编译原理实验指导书

编译原理实验指导 书

《编译原理》实验指导书 太原科技大学计算机学院 -3-1

序 《编译原理》是国内外各高等院校计算机科学技术类专业,特别是计算机软件专业的一门重要专业课程。该课程系统地向学生介绍编译程序的结构、工作流程及编译程序各组成部分的设计原理和实现技术。由于该课程理论性和实践性都比较强,内容较为抽象复杂,涉及到大量的软件设计和算法,因此,一直是一门比较难学的课程。为了使学生更好地理解和掌握编译原理和技术的基本概念、基本原理和实现方法,实践环节非常重要,只有经过上机进行程序设计,才能使学生对比较抽象的教学内容产生具体的感性认识,增强学生综合分析问题、解决问题的能力,并对提高学生软件设计水平大有益处。 为了配合《编译原理》课程的教学,考虑到本课程的内容和特点,本指导书设置了七个综合性实验,分别侧重于词法分析、NFA的确定化、非递归预测分析、算符优先分析器的构造、LR分析、语义分析和中间代码的生成、基于DAG的基本块优化,以支持编译程序的各个阶段,基本涵盖了《编译原理》课程的主要内容。 本指导书可作为《编译原理》课程的实验或课程设计内容,在课程教学的同时,安排学生进行相关的实验。实验平台可选择在MS-DOS或Windows操作系统环境,使用C/C++的任何版本作为开发工具。学生在做完试验后,应认真撰写实验报告,内容应

包括实验名称、实验目的、实验要求、实验内容、测试或运行结果等。

目录 实验一词法分析 ........................................................... 错误!未定义书签。实验二 NFA的确定化.................................................... 错误!未定义书签。实验三非递归预测分析 ............................................... 错误!未定义书签。实验四算符优先分析器的构造................................... 错误!未定义书签。实验五 LR分析 .............................................................. 错误!未定义书签。实验六语义分析和中间代码生成................................ 错误!未定义书签。实验七基于DAG的基本块优化................................... 错误!未定义书签。

编译原理实验报告:实验一编写词法分析程序

( 编译原理实验报告 , 实验名称:实验一编写词法分析程序 实验类型:验证型实验 指导教师:何中胜 专业班级:( 13软件四 姓名:丁越 学号: 实验地点:) 秋白楼B720

实验成绩: 日期:2016年 3 月 18 日

一、实验目的 通过设计、调试词法分析程序,实现从源程序中分出各种单词的方法;熟悉词法分析程序所用的工具自动机,进一步理解自动机理论。掌握文法转换成自动机的技术及有穷自动机实现的方法。确定词法分析器的输出形式及标识符与关键字的区分方法。加深对课堂教学的理解;提高词法分析方法的实践能力。通过本实验,应达到以下目标:[ 1、掌握从源程序文件中读取有效字符的方法和产生源程序的内部表示文件的方法。 2、掌握词法分析的实现方法。 3、上机调试编出的词法分析程序。 二、实验过程 以编写PASCAL子集的词法分析程序为例 1.理论部分 > (1)主程序设计考虑 主程序的说明部分为各种表格和变量安排空间。 数组 k为关键字表,每个数组元素存放一个关键字。采用定长的方式,较短的关键字后面补空格。 P数组存放分界符。为了简单起见,分界符、算术运算符和关系运算符都放在 p表中(编程时,还应建立算术运算符表和关系运算符表,并且各有类号),合并成一类。 id和ci数组分别存放标识符和常数。 instring数组为输入源程序的单词缓存。 ¥ outtoken记录为输出内部表示缓存。 还有一些为造表填表设置的变量。 主程序开始后,先以人工方式输入关键字,造 k表;再输入分界符等造p表。 主程序的工作部分设计成便于调试的循环结构。每个循环处理一个单词;接收键盘上送来的一个单词;调用词法分析过程;输出每个单词的内部码。 ⑵词法分析过程考虑 将词法分析程序设计成独立一遍扫描源程序的结构。其流程图见图 1-1。 …

编译原理实验报告

《编译原理》实验报告软件131 陈万全132852

一、需求分析 通过对一个常用高级程序设计语言的简单语言子集编译系统中词法分析、语法分析、语义处理模块的设计、开发,掌握实际编译系统的核心结构、工作流程及其实现技术,获得分析、设计、实现编译程序等方面的实际操作能力,增强设计、编写和调试程序的能力。 通过开源编译器分析、编译过程可视化等扩展实验,促进学生增强复杂系统分析、设计和实现能力,鼓励学生创新意识和能力。 1、词法分析程序设计与实现 假定一种高级程序设计语言中的单词主要包括五个关键字begin、end、if、then、else;标识符;无符号常数;六种关系运算符;一个赋值符和四个算术运算符,试构造能识别这些单词的词法分析程序。 输入:由符合和不符合所规定的单词类别结构的各类单词组成的源程序文件。 输出:把所识别出的每一单词均按形如(CLASS,VALUE)的二元式形式输出,并将结果放到某个文件中。对于标识符和无符号常数,CLASS字段为相应的类别码的助记符;VALUE字段则是该标识符、常数的具体值;对于关键字和运算符,采用一词一类的编码形式,仅需在二元式的CLASS字段上放置相应单词的类别码的助记符,VALUE字段则为“空”。 2、语法分析程序设计与实现 选择对各种常见高级程序设计语言都较为通用的语法结构——算术表达式的

一个简化子集——作为分析对象,根据如下描述其语法结构的BNF定义G2[<算术表达式>],任选一种学过的语法分析方法,针对运算对象为无符号常数和变量的四则运算,设计并实现一个语法分析程序。 G2[<算术表达式>]: <算术表达式>→<项> | <算术表达式>+<项> | <算术表达式>-<项> <项>→<因式>|<项>*<因式>|<项>/<因式> <因式>→<运算对象> | (<算术表达式>) 若将语法范畴<算术表达式>、<项>、<因式>和<运算对象>分别用E、T、F和i 代表,则G2可写成: G2[E]:E → T | E+T | E-T T → F | T*F | T/F F → i | (E) 输入:由实验一输出的单词串,例如:UCON,PL,UCON,MU,ID······输出:若输入源程序中的符号串是给定文法的句子,则输出“RIGHT”,并且给出每一步分析过程;若不是句子,即输入串有错误,则输出“ERROR”,并且显示分析至此所得的中间结果,如分析栈、符号栈中的信息等,以及必要的出错说明信息。 3、语义分析程序设计与实现 对文法G2[<算术表达式>]中的产生式添加语义处理子程序,完成运算对象是简单变量(标识符)和无符号数的四则运算的计值处理,将输入的四则运算转换为四元式形式的中间代码。 输入:包含测试用例(由标识符、无符号数和+、?、*、/、(、)构成的算术表达式)的源程序文件。 输出:将源程序转换为中间代码形式表示,并将中间代码序列输出到文件中。 若源程序中有错误,应指出错误信息 二、设计思路 1、词法分析程序设计与实现 1)单词分类 为了编程的实现。我们假定要编译的语言中,全部关键字都是保留字,程序员不得将它们作为源程序中的标识符;作了这些限制以后,就可以把关键字和标识符的识别统一进行处理。即每当开始识别一个单词时,若扫视到的第一个字符为字母,则把后续输入的字母或数字字符依次进行拼接,直至扫视到非字母、数字字符为止,以期获得一个尽可能长的字母数字字符串,然后以此字符串查所谓保留字表(此保留字表要事先造好),若查到此字符串,则取出相应的类别码;反之,则表明该字符串应为一标识符。

相关文档
最新文档