大连理工大学_编译方法-中间代码及解释程序_第1页
大连理工大学_编译方法-中间代码及解释程序_第2页
大连理工大学_编译方法-中间代码及解释程序_第3页
大连理工大学_编译方法-中间代码及解释程序_第4页
大连理工大学_编译方法-中间代码及解释程序_第5页
已阅读5页,还剩45页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、第七章 语义分析与代码生成7.1 语法制导翻译编译程序的实质性工作是翻译,即为源程序生成目标代码。为此,我们必须知道程序的含义是什么(语义分析)?应该翻译成什么(代码生成)?在三、四章,我们主要讨论了源程序的识别,即判定一个源程序是否符合源语言的文法。在讨论语法分析时曾说过,上下文无关文法不足以描述编程语言的全部语法特征。为了说明这一点,让我们来看一个例子:VARi:integer;BEGINj:=i*iEND;如果j没有在外层块中说明,那么赋值语句中出现的j就是非法的。这是一种上下文敏感的成分。为了清楚地说明这一点,假定j是在自顶向下分析过程中由非终极符<变量>导出的,在这次推导

2、之前的句型为VARj:integer;<变量>其中,为符号串。推导后的句型为VARj:integer;j为了保证变量在使用前必须说明,需要有如下形式的规则:VAR j:integer;<变量>VAR j:integer;j而不是<变量>j即<变量>只有在一定的上下文中才可以展开成j。上下文敏感成分的分析实质上是语法分析的内容。但是,因为我们的语法分析是以上下文无关文法为基础的,没有考虑上下文敏感成分的处理,所以必须在语义分析时加以考虑。这相当于把语言的敏感成分划归为语言的语义范畴(尽管在概念上并非如此)。比如说在处理说明VAR j:integer

3、时,语义分析应该将这个说明所提供的信息填入符号表,即完成对符号表的插入操作;当然也需要完成其它的语义分析工作,而后在确定是否可用规则<变量>j进行推导时,就可通过对符号表的检索操作来完成。如果符号表中有标识符j,而且j又是个变量标识符,就可以用此规则进行推导。除了敏感成分的处理之外,为了生成目标代码,所需要完成的一切操作都属于语义分析的范畴。考虑如下条件语句:IF E THEN S1 ELSE S2为它生成的目标代码应具有图7.1的结构(称为它的目标结构),其中,计算E的目标指令、S1和S2的目标指令是在处理表达式和语句时生成的,处理条件语句时所生成的指令就是“jumpf l0”和

4、“jump l1”。前者的含义为表达式E的值为false时,程序转向l0的指令继续执行,后者为无条件转到l1的指令执行。问题在于编译程序在处理条件语句时是从左向右进行的。因此,当要生成jumpf指令时,不知道l0的值,因为S1的目标这时尚未生成,不知道它究竟有多少条指令。在生成jump这条指令时也有同样问题。为了解决这个问题,在生成jumpf和jump指令时,应先记录这两条指令本身的位置,等以后再回填它们的转向目标。假设当前要生成的指令位置为lc,条件语句的处理算法如下:IF sy=ifsyTHENinsymbol;expression;处理表达式IF表达式类型< >boolsTH

5、ENerror (n)ENDIF;lc1:=lc;生成jumpf指令;lc:=lc+1;IF sy=thensy THENinsymbol;statement;处理语句IF sy=elsesy THENlc2:=lc;生成jump指令;lc:=lc+1;回填jumpf指令的转向目标;insymbol; 图7.1statement;处理语句回填jump指令的转向目标;ELSE回填jumpf指令的转向目标ENDIFENDIFENDIF;可以看出,除了检查表达式类型外(敏感成分的处理),语义分析工作还包括转向目标的回填等操作。与第四章给出的条件语句的语法分析算法相比,上述算法只是增加了如下几个操作:

6、<op1>:IF表达式类型< >bools THENerror(n);ENDIF;lc1:=lc;生成jumpf指令;lc:=lc+1;<op2>:lc2:=lc;生成jump指令;lc:=lc+1;回填jumpf指令的转向目标;<op3>:回填jump指令的转向目标;<op4>:回填jumpf指令的转向目标;这相当于说上面的处理算法是根据如下文法规则写成的:<IF语句>IF<表达式><op1>THEN<语句>ELSE<op2><语句><op3><

7、;IF语句>IF<表达式><op1>THEN<语句><op4>即在文法规则中嵌入了相应的语义加工操作。于是,语义分析及代码生成可以随着语法分析的进行,通过嵌入相应的语义加工操作来完成。这种方法称为语法制导翻译,因为语言的文法规则确定了相应的语义分析类型及生成代码的性质,而且分析算法的主体控制是相应的文法规则。本章后面将结合实例讨论各种典型的语言结构的语义分析及代码生成。7.2 目标机为了完成代码生成工作,必须有一个提供运行环境的目标机。最直接的方法是,在哪个机器上运行的编译程序就生成那个机器的目标代码,或生成那个机器的汇编语言程序,然后经过

8、汇编程序汇编成可以执行的机器语言程序。汇编后产生的目标代码可以具有绝对地址,从而可以装到内存的固定区域去执行;也可以具有浮动的(相应的)地址,再由装入程序(或者是连接装配程序)来为地址代真(即转换成绝对地址),即可用于执行。无论是哪一种情况,都需要知道机器的硬件,诸如有多少个累加器、特殊寄存器、地址空间的大小等。但事实上,代码生成也可先脱离特定的硬件环境。一种逐渐流行的方法是为一个抽象机生成代码,然后,在特定的机器上写一个解释程序来解释抽象机的指令。下面我们将介绍一个抽象机,它是专为PASCAL-S设计的,与任何特定的计算机无关,不妨称为PASCAL-S处理机。尽管PASCAL-S处理机在硬件

9、上并不存在,但它的指令不难落实到任何特定的计算机上。PASCAL-S处理机上有如下一些寄存器和一个存贮区:ps:程序状态字ir:指令寄存器pc:指令计数器t:栈顶寄存器b:基地址寄存器display:地址寄存器组存贮区分为程序存贮区、表格区和栈区,如图7.2所示。程序存贮区CODE用于存放目标代码,这部分存贮区在目标代码的执行期间保持不变,可看作只读存贮(ROM)。表格区用来存放程序的静态信息。栈区用作程序执行的数据空间。栈区由一系列数据段组成,每个数据段包括如下内容:图7.2(1)标记部分。(2)参数部分(可能为空)。(3)局部量。(4)处理语句时所需要的临时工作空间。对应PASCAL-S中

10、过程或函数的数据区,标记部分用来存放(1)函数的返回结果。(2)过程或函数的返回地址。(3)静态链。(4)动态链。(5)过程或函数标识符在tab中的位置。其中静态链与动态链指向栈区S的其它单元,返回地址指向代码区CODE中的单元,第(5)项则指向表格区中的单元。PASCAL-S处理机是一个栈式的机器,它没有传统的累加器,所有对数据的操作均在栈顶进行。例如,加法指令是把栈顶两个单元的内容相加,并把结果留在栈顶;条件转向指令根据栈顶单元的内容决定是否转向,等等。下面我们来介绍PASCAL-S机的指令系统。因为这个指令系统是根据源语言PASCAL-S的特点而设计的,所以为了深刻理解各指令的意义,需要

11、与后面将讨论的目标结构结合起来学习。每条指令的格式为操作码操作数1操作数2当然,有些指令只有一个操作数,还有的指令没有操作数。1.双操作指令(4条)LODA:0xy将x(层号)和y(位移量)所确定的地址装到栈顶。LODV:1xy根据x(层号)和y(位移量)确定单元地址,把单元的内容装到栈顶。LOD*:2xy根据x(层号)和y(位移量)确定存放地址的单元,把那个地址所指单元的内容装到栈顶。UPD:3xyx,y均为层号,根据静态链更新display从第x+1层到第y层的内容。 2.单操作指令(23条)STAD:8y调用y所确定的标准函数,自变量的值在栈顶,调用后的函数值取代原来的栈顶。表7.1中给

12、出了编号y与标准函数的对应关系。表7.1y函 数y函 数y函 数0整数abs7succ14ln1实数abs8pred15sqrt2整数sqr9round16arctan3实数sqr10trunc17eof4 odd11sin18eoln5 chr12cos6 ord13expADDL:9y将栈顶单元的内容加y。JUMP:10y转到y所指的指令继续执行。JUMPF:11y当栈顶单元的内容为false时退掉栈顶单元并转到y所指的指令去执行。JUMPX:12yy为情况表的入口地址。根据栈顶单元的内容从情况表中确定转向目标,然后完成转向并退掉栈顶单元。ENTRY:13yENTRY总是成对出现。第一条中

13、的y为情况标号的值,第二条中的y为相应的情况子句的入口地址。CASE语句的情况表由若干对ENTRY组成。FORIUP:14yFOR1UP在如下形式的循环语句中用作循环的入口条件测试。FOR i:=E1 TO E2 DOS S当执行到FOR1UP指令时,运行时栈的状态如图7.3所示。图7.3指令FOR1UP在初值小于终值时,为循环变量i赋初值,否则退掉栈顶三个单元并转到y(循环出口)所指的指令继续执行。FOR2UP:15yFOR2UP与FOR1UP配对使用。FOR2UP用于循环的重复条件测试。如果循环变量的值小于终值,则循环变量的值加上1,并转循环入口y;否则退掉栈顶三个单元。FOR1DOWN:

14、16yFOR2DOWN:17y这两条指令与前两条类似,它们用于如下形式的循环语句中:FOR i:=E1 DOWNTO E2 DO SMARK:18y这条指令为过程语句或函数调用的第一条指令,y为过程或函数标识符在符号表的位置。该指令在栈顶分配5个单元作为标记部分,并将y填入所分配的第5个单元中。CALL:19y这条指令为过程或函数调用的最后一条指令。y为btab j.psize1;由于栈顶指针t指向参数区最后一个单元,所以t-y恰为本层数据区的开始位置。在MARK与CALL之间的指令用于为形参分配存贮。CALL指令用于填写本层display内容(t-y);填写标记部分(静态链,动态链,返回地址

15、);为局部量分配存贮,根据标识符表中的入口地址转过程体。INDEX1:20yINDEX:21y这两条指令是为计算数组元素的地址而设计的,y是指向数组表的指针。它的功能是将(栈顶单元内容数组下界)*L加到次栈顶单元的内容上并退掉栈顶单元。在INDEX1中,L=1;在INDEX中,L=atab y.elsize。LODB:22y将一串相连单元的内容装到栈顶,这串单元的个数由y指定,第一个单元的地址在原来的栈顶单元中,修改栈顶指针。COPYB:23y以栈顶单元所包含的地址为开始地址,复制y个单元的内容到次栈顶所含地址为开始的y个单元中。LODL:24y把y装到栈顶。LODR:25yy为指向实数表的指

16、针。这条指令是将y所指向的实数装到栈顶。FLOAT:26y将栈中从栈顶开始数的第y个单元的内容变成浮点数。READ:27y将y所指定类型的数据读到栈顶单元的内容所指定的单元中,并退掉栈顶单元。WRITE0:28yy为指向串表stab的指针。这条指令是输出从y开始的一串字符,(输出字符的个数在栈顶单元中)并退掉栈顶单元。WRITE1:29yWRITE2:30y这两条指令是输出简单变量的值,变量类型由y指定。WRITE1输出栈顶单元的内容并退掉栈顶单元(输出域宽为缺省值)。WRITE2按栈顶单元所示域宽输出次栈顶的内容并退掉栈顶两个单元。 3.无操作数指令(33条)HALT:31终止目标程序执行。

17、EXITP:32这条指令是从过程返回的指令,它将栈顶指针恢复到执行MARK前的值;根据动态链恢复基地址寄存器的内容;按返回地址完成返回。EXITF:33这条指令是从函数返回的指令,它将栈顶指针恢复到执行MARK前的值加1(栈顶为函数结果);根据动态链恢复基地址寄存器的内容;按返回地址返回。FETCH:34以栈顶单元的内容作地址,取这个地址所指单元的内容回送到栈顶。NOT:35将栈顶单元的内容求反(栈顶单元的内容为布尔值)。NEGATE:36将栈顶单元的内容乘上-1。WRITER:37这条指令的意义是用如下语句打印实数x:write (x:y:z);其中z在栈顶单元中,y在次栈顶单元中,而x在栈

18、顶第三个单元中。STORE:38将栈顶单元的内容存到次栈顶单元所指示的单元中。READLN:62WRITELN:63这两条指令分别等价于PASCAL语言中的readln与writeln语句。以下指令均是在栈顶两个单元间进行的数据操作,功能是 <次栈顶><次栈顶><运算><栈顶>并退掉栈顶单元。表7.2给出了各种运算所对应的操作码及助记符。表7.2运 算助记符操作码运 算助记符操作码实数相等EQR39逻辑加OR51实数不等NEQR40逻辑与AND56实数小于LTR41实数小等LER42整数加ADDI52实数大于GTR43整数减SUBI53实数大等G

19、ER44实数加ADDR54整数相等EQI45实数减SUBR55整数不等NEQI46整数乘MULI57整数小于LTI47整数除DIVI58整数小等LEI48求余数MODI59整数大于GTI49实数乘MULR60整数大等GEI50实数除DIVR61我们之所以这样设计目标机,一方面是因为可以避免考虑特定计算机的具体细节,另一方面是因为可以简化编译程序的代码生成。然而,由于这个目标机在实际上并不存在,因此,为了使目标程序可以真正执行,必须设法实现这个目标机。我们的办法是用PASCAL语言写一个解释程序来执行这个目标机的指令。目标机的各寄存器及存贮区可以用PASCAL的各种变量来模拟,比如:CONSTm

20、axlevel=7;tabsize=100;atabsize=30;btabsize=20;rconstsize=20;stabsize=600;codesize=800;stacksize=1450;TYPEobject=(konstant,vvariable,typel,prozedure,funktion);types=(notyp,ints,reals,bools,chars,arrays,records);order=PACKEDRECORDf:0.63;x:0.maxlevel;y:integerEND;VARps: (run,finish,error);程序状态字ir : ord

21、er;指令寄存器pc: integer;指令计数器t: integer;栈顶寄存器b: integer;基地址寄存器display: ARRAY 0.maxlevel OF integer;地址寄存器组code: ARRAY 0.codesize OF order;代码区tab: ARRAY 0.tabsize OFPACKED RECORDname:PACKED ARRAY 1.10 OF char;link:0.tabsize;obj:object;typ:types;ref:integer;normal:boolean;lev:0.maxlevel;adr:integerEND;atab

22、: ARRAY 1.atabsize OF PACKED RECORDinxtyp:types;eltyp:types;elref:integer;low:integer;hig:integer;elsize:integer;size:integerEND;btab:ARRAY 1.btabsize OF PACKED RECORDlast,lastpar:0.tabsize;psize,vsize:integerEND;rconst:ARRAY 1.rconstsize OF real;stab:PACKED ARRAY 0.stabsize OF char;s:ARRAY 1.stacks

23、ize OF 栈区RECORDCASE cn:types OFints: (I:integer);reals: (r:real);bools: (b:boolean);chars: (c:char)END;目标程序的执行则可以用如下语句模拟:各寄存器及栈区初始化;REPEATir:=code pc;pc:=pc+1;CASE ir.f OFLODA:t:=t+1;st.i:=display ir.x+ir.y;LODV:t:=t+1;st.i:=sdisplayir.x+ir.y;LOD*:t:=t+1;st:=ssdisplayir.x+ir.y.i;UPD:h1:=ir.y; h2:=ir

24、.x; h3:=b; REPEAT displayh1:=h3; h1:=h1-1; h3:=sh3+2.i UNTIL h1=h2;STAD:CASE ir.y OF 0:st.i:=abs(st.i); 1:st.r:=abs(st.r); 16:st.r:=atan(st.r); 17:st.b:=eof(prd); 18:st.b:=eoln(prd)ENDCASE;ADDL:st.i:=st.i+ir.y;JUMP:pc:=ir.y;JUMPF:IF NOT st.b THEN pc:=ir.y ENDIF; t:=t-1;JUMPX:h1:=st.i;t:=t-1;h2:=ir.y

25、;h3:=0;REPEATIF codeh2.f<>ENTRY THENh3:=1;ps:=caschk情况语句出错ELSEIF codeh2.y=h1 THENh3:=1;pc:=codeh2+1.yELSEh2:=h2+2ENDIFENDIFUNTIL h3<>0;FOR1UP:h1:=St-1.i;IF h1<=st.i THEN sst-2.i.i:=h1ELSE t:=t-3; pc:=ir.yENDIFFOR2UP:h2:=st-2.i; h1:=sh2.i+1; IF h1<=st.i THEN sh2.i:=h1; pc:=ir.yELSE

26、t:=t-3ENDIF;FOR1DOWN:h1:=st-1.i;IF h1>=st.i THEN sst-2.i.i:=h1ELSE pc:=ir.y; t:=t-3ENDIF;FOR2DOWN:h2:=st-2.i; h1:=sh2.i-1IF h1>=st.i THEN sh2.i:=h1; pc:=ir.yELSE t:=t-3ENDIF;MARK:h1:=btabtabir.y.re.vsize;t:=t+5;st-1.i:=h1-1;st.i:=ir.y;CALL:h1:=t-ir.y; h2:=sh1+4.i h3:=tabh2.lev; displayh3+1:=h1

27、; h4:=sh1+3.i+h1 sh1+1.i:=pc; sh1+2.i:=displayh3; sh1+3.i:=b;FOR h3:=t+1 TO h4 DO sh3.i:=0ENDFOR;b:=h1;t:=h4;pc:=tabh2.adr;INDEX1:h1:=ir.y; h2:=atabh1.low; h3:=st.i;IF h3<h2 THEN ps:=inxchk 下标越界错ELSE IF h3>atabh1.high THEN ps:=inxchk ELSE t:=t-1; st.i:=st.i+(h3-h2) ENDIFENDIF;INDEX:h1:=ir.y; h

28、2:=atabh1.low; h3:=st.i;IF h3<h2 THEN ps:=inxchkELSE IF h3>atabh1.high THEN ps:=inxchk ELSE t:=t-1; st.i:=st.i+(h3-h2)*atabh1.elsize ENDIFENDIF;LODB:h1:=st.i;t:=t-1;h2:=ir.y+t;WHILE t<h2 DO BEGIN t:=t+1; st:=sh1 h1:=h1+1 END;COPYB:h1:=st-1.i; h2:=st.i; h3:=h1+ir.y; WHILE h1<h3 DO BEGIN s

29、h1:=sh2; h1:=h1+1; h2:=h2+1 END; t:=t-2;LODL:t:=t+1;st.i:=ir.y;LODR:t:=t+1;st.r:=rconstir.y;FLOAT:h1:=t-ir.y; sh1.r:=sh1.i;READ:IF eof(prd) THEN ps:=redchk 读错ELSE CASE ir.y OF ints:read(prd,sst.i.i); reals:read(prd,sst.i.r); chars:read(prd,sst.i.c); ENDCASE;t:=t-1;WRITE0:h1:=st.i; h2:=ir.y; t:=t-1;

30、REPEAT write(prr,stabh2); h1:=h1-1; h2:=h2+1UNTIL h1=0;WRITE1:CASE ir.y OFints:write(prr,st.i:fld1);reals:write(prr,st.r:fld2);bools:IF st.b THENwrite(true)ELSEwrite(false)ENDIF;chars:write(prr,chr(st.i)ENDCASE;t:=t-1;WRITE2:CASE ir.y OFints:write(prr,st-1.i:st.i);reals:write(prr.s1.r:st.i);bools:IF

31、 st THENwrite(true)ELSEwrite(false)ENDIF;chars:write(prr,chr(st-1.i:st.i)ENDCASE;t:=t-2;HALT:ps:=finish; 正常停机EXITP:t:=b-1;pc:=sb+1.i;b:=sb+3.i;EXITF:t:=b;pc:=sb+1.i;b:=sb+3.i;FETCH:st:=sst.i;NOT:st.b:=NOTst.b;NEGATE:st.i:=-st.i;WRITER:write(prr,st-2.r:st-1.i);t:=t-3;STORE: sst-1.i:=st;t:=t-2;EQR:t:=

32、t-1; st.b:=st.r=st+1.r;NEQR:t:=t-1;st.b:=st.r< >st+1.r; DIVR:t=t-1;st.r:=st.r/st+1.r;READLN:readln;WRITELN:writeln; ENDCASE;UNTIL ps< >run;IF ps< >finish THEN 运行时错误处理;这就是解释执行的过程。这个解释程序的结构非常简单,几乎就是一个情况语句。事实上,高级语言的解释程序结构基本都是这样。7.3 说明部分的处理说明部分给语言带来了一定的冗余度。有些语言(如LISP)没有说明语句;有些语言虽然有说明语句

33、(如BASIC,FORTRAN),但很少需要用;而其它语言(如PASCAL,ADA),则要求程序中所使用的每个实体都必须说明,说明的作用是把程序中的实体与某个标识符联系起来,用标识符作为这个实体的名字。通过缺省的命名规则(如FORTRAN中以I,J,K,L,M,N开头的名字若不特别说明则是用来标识整型变量的)或根据名字所出现的上下文(如在SNOBOL中,TEXT=STRINGA意味着TEXT是一个字符串的名字),说明的使用是可以避免的。然而,人们并不总是希望避免使用说明,特别是当软件开发的主要目标是生产可靠的软件产品时更是这样。因为说明的作用是明确地声明程序员打算怎样使用所说明的实体,而编译程

34、序根据某个实体的上下文可以推断它的使用情况。如果后者与前者不一致,编译程序就可以检查出来并通知给程序员,而如果不使用说明,这类错误就无法检查出来。因此,需要说明部分的语言可以编出较为可靠的程序。从语义分析及代码生成的角度看,编译程序在处理说明部分时的主要任务是:(1)分离出说明语句中所说明的每一个实体,并把它填到符号表中。(2)在符号表中填入尽可能多的有关实体的属性。从而,编译程序可以根据符号表中的信息来检查将来对某个实体的引用是否正确,并根据有关属性为源程序生成正确的目标代码。下面我们将结合PASCAL-S的说明部分加以讨论。7.3.1 常量说明常量说明的作用在于提高程序的可读性与修改方便。

35、例如在上一节,我们利用常量说明定义了栈区S的大小: stacksize=1450;由此可以很容易看出, IF t>stacksize THEN是在测试栈是否溢出。如果需要修改栈区大小,只需修改常量说明,程序中所有用到stacksize的地方(如测试栈溢出的地方)就都同时得到了修改,而无需逐一进行修改。PASCAL-S中常量说明的定义为 <常量说明>CONST<标识符><op1>=<常量><op2><标识符><op1>=<常量><op2>请注意,文法中已嵌入了语义加工操作。处理完标识

36、符后,可以知道常量的名字;处理完常量后可以知道常量的类型(整型、实型、字符型等)及其值。设id定义为 id:PACKED ARRAY1.10 OF char;用来存放所拼出的标识符,c定义为c:RECORDCASE tp:types OFints,chars,bools: (i:integer);reals: (r:real)END;用来存放常量的类型及其值(字符型及布尔型常量的值是用其序号代表的),则嵌入的语义操作为:<op1>:enter(id,konstant);<op2>: tabt.typ:=c.tp;IF c.tp=reals THEN enterreal

37、(c.r); tabt.adr:=c1ELSE tabt.adr:=c.iENDIF;这两个操作对符号表完成了一次完整的插入(见5.2节)。相应的,常量的定义为<常量><字符常量><op1>|+|-<op2><无符号数><op3>|+|-<op2><常量标识符><op4>嵌入的语义操作如下:<op1>:c.tp:=chars;c.i:=inum;字符值的序号<op2>:sign:=1;IF sy=minus THEN sign:=-1ENDIF;<op3&g

38、t;:IF sy=intcon THEN c.tp:=ints; c.i:=sign*inumELSE IF sy=realcon THEN c.tp:=reals; c.r:=sign*rnum ENDIFENDIF;<op4>:x:=loc<id>检索标识符表IF (x< >0) AND (tabx.obj=konstant) THEN c.tp:=tabx.typ; IF c.tp=reals THEN c.r:=sign*rconsttabx.adr ELSE IF c.tp=ints THEN c.i:=sign*tabx.adr ENDIF EN

39、DIFENDIF;根据这两个嵌入语义操作的文法规则,不难写出常量说明的处理算法。7.3.2 类型说明为数据规定类型可以更自然地描述数据,因而适于对实际问题的抽象。类型实质上代表着一组值以及可在这组值上施加的一组操作。例如,integer表示整数类型,它与集合,-2,-1,0,1,2,+,-,*,div,:=,<,有关,即说明为integer的变量i可以取第一个集合中的值,可以参与第二个集合中的运算。当然,类型可以在定义数据对象时引入,而不是非有名字不可。但利用类型说明为类型赋以名字更能反映类型概念本身,而且用有名类型来定义数据更能反映对数据的抽象。比较下面两个说明:1.VARr1,r2,

40、r3:RECORDname:PACKED ARRAY1.20OF char;age:integer;sex: (male,female)END;2.TYPEpersonal message=RECORDname:PACKED ARRAY1.20OF char;age:integer;sex: (male,female)END;VARr1,r2,r3:personal message;第一个说明定义了r1,r2,r3为一个记录,它们将来可存放这种记录的值。第二个说明先定义了personal message(个人信息)为一个记录类型,然后定义r1,r2,r3为个人信息,它们将来可用于存放有关个人的

41、信息。显然第二个说明更自然些。PASCAL-S中,类型说明的定义为<类型说明>TYPE<标识符><op1>=<类型><op2><标识符><op1>=<类型><op2>处理完标识符后,需要将这个标识符(类型的名字)填到标识符表中,其它一些属性如typ,ref及adr等,则需在处理完类型后回填。但处理类型的时候,可能会对标识符表完成其它的插入操作,如对记录类型就需要将各域标识符插入标识符表中。因此,为了在标识符表中回填属性,需要记下类型名字在标识符表中的位置。于是,嵌入的语义操作为<o

42、p1>:enter(id,type1); t1:=t;<op2>:tabt1.typ:=tp; tabt1.ref:=rf; tabt1.adr:=sz;其中tp,rf,sz由处理类型的过程返回。关于类型的处理,我们将在讨论变量说明时一起介绍。7.3.3 变量说明变量说明是显式地为程序中所使用的变量命名并规定属性。由于变量在程序运行时拥有值,编译程序必须为变量分配存贮空间,并赋予变量一个地址,且当存贮分配是动态进行时,这个地址只能是相对地址。为变量分配存贮空间的大小取决于变量的数据类型。在PASCAL-S中,由于允许过程的递归调用,所以存贮分配是动态进行的,编译时所做的工作是

43、为变量分配位移量。变量说明的语法规则如下:<变量说明>VAR<标识符><op1>,<标识符><op2>:<op3><类型><op4><标识符><op1>,<标识符><op2>:<op3><类型><op4>当处理完逗号分隔的标识符时,尚不知其类型,所以无法填类型及地址属性,必须等处理完类型后回填。因此,需要记录这些标识符在标识符表tab中的位置(注意,这些标识符在标识符表tab中是连续存放的)。如果dx作为位移量分配的

44、计数器(它在处理一个块的开始时被初始化),则各语义操作如下:<op1>:t0:=t;enter(id,vvariable);<op2>:enter(id,vvariable);<op3>:t1:=t;<op4>:WHILE t0<t1 DO BEGINt0:=t0+1;WITH tabt0DOtyp:=tp;ref:=rf;normal:=true;adr:=dx;dx:=dx+szENDWITHEND;其中tp、rf、sz由处理类型的过程返回。现在我们来讨论类型的处理。首先考虑最简单的情况<类型><类型标识符>&l

45、t;op1>此时,类型的处理算法很简单,只需执行对符号表的检索操作并取出相应的属性即可。<op1>:x:=loc(id);IF(x< >0)AND(tabx.obj=typel)THENtp:=tabx.typ;rf:=tabx.ref;sz:=tabx.adr;ENDIF;其中各属性是在处理类型说明时填入的(或预定义的)。当引入新的数组或记录类型时,相应的算法要复杂些,它必须将这种新类型的信息填到数组表或块表中,而且,在处理当前的新类型时,又必须递归地调用它本身去处理数组的基类型或记录域的类型。下面我们先讨论记录类型的处理。<类型>RECORD<

46、;op1><标识符><op2>,<标识符><op3>:<op4><类型><op5>END<op6>处理记录时,需要将记录的信息填到块表中,而记录的各个域则需填进标识符表tab,有些属性需要回填。由于各个域标识符是局部于它所在的记录的,所以在处理各个域之前需要执行符号表的进层操作,而各个域处理完后要执行符号表的复位操作。算法如下:<op1>:enterblock;level:=level+1;displaylevel:=b;offset:=0;tp:=records;rf:=b;&l

47、t;op2>:t0:=t;enter(id,vvariable);<op3>:enter(id,vvariable);<op4>:t1:=t;调用处理类型的过程;返回eltp,elrf,elsz<op5>:WHILE t0<t1 DO BEGINt0:=t0+1;tabt0.typ:=eltp;tabt0.ref:=elrf;tabt0.normal:=true;tabt0.adr:=offset;offset:=offset+elsz;END;<op6>:sz:=offset;btabrf.vsize:=offset;btabrf.

48、psize:=0;level:=level-1;最后,我们讨论数组类型的处理。<类型>ARRAY<op1><常量><op2>.<常量><op3>,<op4><常量><op2>.<常量><op3>OF<类型><op5>它要求在数组表中建立如图7.4的串记录并返回rf及size,其中elsizen=elsz elsz由处理类型的过程返回elsizei=sizei+1 1<=i<nsizei=(hi-li+1)*elsizei 1&l

49、t;=i<=n可以看出,这一串记录的建立是一个递归的过程,相当于把给定的多维数组看作是ARRAY l1.h1 OFARRAY l2.h2 OFARRAY ln.hn OF<类型>图7.4这样处理的好处是使数组元素的地址计算得到了简化。具体算法如下:<op1>:tp:=arrays;<op2>:IF low.tp=reals THEN error(n)ENDIF;low由处理常量的过程返回<op3>:IF high.tp < > low.tp THEN error(n)ENDIF;high由处理常量的过程返回enterarray(

50、low.tp,low.i,high.i);rf:=a;<op4>:eltp:=ARRAY;递归地调用处理数组的过程;<op5>:atabrf.eltyp:=eltp;atabrf.elref:=elrf;atabrf.elsize:=elsz;atabrf.size:=(high.i-low.i+1)*elsz;sz:=atabrf.size;7.3.4 子程序说明子程序使我们能利用语言所提供的基本结构来定义更高级的操作,从而,在编译时可以不受语言的基本结构的束缚,尽量使用问题本身的术语来描述它的解,就好象语言是可扩充的一样。PASCAL-S中有两种形式的子程序过程与函

51、数。它们都是在更高的层次上为一组操作命名,函数区别于过程的主要点是它需要返回一个值。子程序说明是用来定义子程序的,包括两个部分:一部分定义子程序与外界的接口,或称调用约定;这部分包括子程序名、参数的数目、类型及传递方式(对函数来说,返回值的类型也在这个接口中提供)。另一部分定义了子程序的操作,称为子程序体;这部分包括一些局部量的定义以及实现子程序操作的一些语句。子程序定义后要在调用时执行。为了实现这一点,编译程序在处理子程序说明时必须为子程序生成目标代码,以便在调用子程序时执行。子程序的目标结构为入口地址:实现子程序操作的各语句的目标代码返回指令此外,为了检查对子程序的调用是否正确,处理子程序

52、说明时还必须将有关调用约定的信息保存起来。以过程说明为例,在扫描到过程标识符时,应将它登记在标识符表中,并在块表中建立相应记录。但有些信息在此时是无法填入的,因此要记住标识符及块表中相应记录的位置,以便回填。在处理参数表时,将各参数的属性如参数类型,传递方式(值参或变量参数)等插入标识符表中。参数表处理完后,需要回填块表中相应记录的lastpar及psize域。过程的各局部量说明及定义过程操作的语句部分要由各种说明的处理过程及语句的处理过程分别处理,结果是为各种局部量分配存贮(包括在表格区的分配及位移量分配),为过程体的语句生成代码。在此期间,需要回填块表中相应记录的vsize域以及标识符表中相应记录的adr域(过程入口地址)。当整个过程说明处理完后,要生成返回指令EXITP。注意,过程的形式参数及局部量是局部于过程的,所以在处理参数表之前要执行对符号表的进层操作,而在过程说明处理完后要执行复位操作。嵌入语义操作的文法规则为<过程说明>PROCEDURE<标识符><op1><形参表><op2><说明部分>;<op3><复合语句>;<op4>各语义操作为<op1>:enter(id,pr

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论