语法制导翻译和中间代码生成

本文参考《编译原理(第三版)》

目录

语义分析概述

  • 编程语言的语法和语义之间并没有明确界限,语义可完成 CFG 无法描述的特性
    • 如: 标识符先声明后引用的语法规定,可用简单的抽象语言说明: L = { W c W ∣ W ∈ ( a ∣ b ) ∗ } L=\{WcW|W∈(a|b)^*\} L={WcW∣W∈(a∣b)∗},这不是一个上下文无关语言,无法用前面章节介绍的语法分析方法来处理
    • 再如: L = { a n b m c n d m ∣ n ≥ 0 , m ≥ 0 } L=\{a^nb^mc^nd^m|n≥0,m≥0\} L={anbmcndm∣n≥0,m≥0},它是检查过程声明的形参个数和过程引用的实参个数应该一致的问题的抽象。 a n a^n an 和 b m b^m bm 代表两个过程声明的形参表中分别有 n n n 和 m m m 个参数; c n c^n cn 和 d m d^m dm 代表两个过程调用的实参表;实参和形参个数的一致性检查也是放在语义分析阶段完成

语义分析任务

  • 静态检查—语义检查。主要进行一致性检查和越界检查,能在程序运行之前发现编程错误
    • 上下文相关性
      • 例如:任何作用域内同一个标识符最多只能声明一次; 一个 b r e a k break break 语句必须处于一个循环或 s w i t c h switch switch 语句之内;赋值表达式的左部表示的是一个左值…
    • 类型检查
      • 例如:自动类型转换 (在语法树中插入一个运算符来表示这个转换)
  • 如果静态语义正确,执行真正的翻译(翻译成中间代码或直接生成实际目标代码)—语义处理
    • 说明语句:将其中定义的名字及属性记录在符号表中,以便进行存储分配
    • 执行语句:生成语义上等价的中间代码

语法制导翻译

语法制导翻译(syntax-directed translation)是目前大多数编译程序采用的一种技术

语法制导翻译

  • 在语法分析中,根据每个产生式所对应的语义子程序(语义规则描述的动作)进行翻译的方法称为语法制导翻译;其基本思想是:将语言结构的语义属性的形式赋予代表此结构的文法符号。在语法分析推导或归约的每一步中,通过语义规则完成对属性的计算,以实现语义处理
  • 在描述语义动作时,需要赋予每个文法符号 X X X(非终极符和终极符)不同的属性值。属性可以是类型、地址、值、符号表内容等,用记号 X . t y p e X.type X.type, X . v a l X.val X.val 等表示
    • 综合属性
      • 一个结点的综合属性值是从其子结点的综合属性值“计算”得来的,可通过自底向上进行求值,用 ↑ ↑ ↑ 表示
    • 继承属性
      • 继承属性则是由语法树中该结点的父结点和位于其左边的兄弟结点的属性值“计算”出来的,继承属性用于自上而下传递信息
  • 非终结符既可有综合属性也可有继承属性,但文法开始符号没有继承属性终结符只有综合属性,它们由词法分析程序提供

注释分析树

  • 每个结点都带有属性值的语法分析树称为注释分析树
  • 计算结点属性值的相关活动称为注释或装饰语法树

语法制导定义

  • 在一个语法制导定义中, ∀ A → α ∈ P \forall A→\alpha \in P ∀A→α∈P, 都有与之相关联的一套语义规则,每条规则的形式为
    b : = f ( c 1 , c 2 , … , c k ) b:= f (c_1, c_2, …, c_k) b:=f(c1​,c2​,…,ck​)
    • f f f 是一个函数,满足:
      • b b b 是 A A A 的一个综合属性并且 c 1 , c 2 , … , c k c_1, c_2, …, c_k c1​,c2​,…,ck​ 是产生式右边 α \alpha α 中的文法符号的属性
      • b b b 是产生式右部 α \alpha α 中的某个符号的一个继承属性,并且 c 1 , c 2 , … , c k c_1, c_2, …, c_k c1​,c2​,…,ck​ 是 A A A 或 α \alpha α 中的任何文法符号的属性

:表达式求值的语法制导定义
语法制导翻译和中间代码生成

:表达类型定义的语法制导定义( L . i n L.in L.in 为继承属性)
语法制导翻译和中间代码生成

  • i d . e n t r y id.entry id.entry 表示 i d id id 在符号表中的地址
  • a d d t y p e addtype addtype 将 L . i n L.in L.in 存入 i d id id 的符号表中
  • 一个产生式中同一符号多次出现,用上角标来区分
    • 例如, E → E + E E\rightarrow E+E E→E+E 表示为 E → E ( 1 ) + E ( 2 ) E\rightarrow E^{(1)} +E^{(2)} E→E(1)+E(2)
  • 每个产生式的语义动作,写在该产生式之后的花括号内

属性文法

  • 翻译文法
    • 在语法制导翻译中,我们可以用一个或多个子程序(称为语义动作)来完成产生式的语义分析,并把这些语义动作插入到产生式中相应位置,从而形成翻译文法。当在语法分析过程中使用该产生式时,就可以在适当的时机调用这些动作,完成所需要的翻译
  • 属性文法
    • 属性文法是在上下文无关文法的基础上,为每个文法符号( V T V_T VT​ 或 V N V_N VN​)配备若干属性;再根据产生式所包含的语义,给出符号间属性的求值规则,从而形成所谓的属性翻译文法,即属性文法
      • 这样,当在语法分析中使用该产生式时,可根据属性求值规则对相应属性进行求值,从而完成翻译
      • 当翻译文法的符号具有属性,并带有属性求值规则时,就称为属性文法
    • 因此,属性文法是一个三元组: A = ( G , V , F ) A=(G, V, F) A=(G,V,F), 其中:
      • G G G:是一个上下文无关文法
      • V V V:有穷的属性集,每个属性与文法的一个终结符或非终结符相连
      • F F F:关于属性的断言或一组属性的计算规则(称为语义规则)。断言或语义规则与一个产生式相联,只引用该产生式左端或右端的终结符或非终结符相关的属性


表达式文法 G [ E ] , E → T + T ∣ T     T → n ∣ b G[E], E\rightarrow T+T|T \ \ \ T\rightarrow n|b G[E],E→T+T∣T   T→n∣b;有关类型匹配的属性文法如下:
语法制导翻译和中间代码生成

综合属性

  • 综合属性求值规则
    • 对终结符号其综合属性具有指定的初始值。在具体实现中,该初始值将由词法分析程序提供
    • 产生式右部的非终结符号的综合属性值,取自后面以该非终结符号为产生式左部时求得的综合属性值
    • 产生式左部的非终结符号的综合属性值,由产生式中左部或右部的某些符号的属性值进行计算
    • 给定一动作符号,其综合属性值将用该动作符号的其它属性值进行计算


一种能够完成输出表达式值的符号串翻译文法如下,右列为相应产生式的属性求值规则
① S → E ↑ q @ A N S W E R ↓ r          r = q ② E ↑ p → E ↑ q + T ↑ r                          p = q + r ③ E ↑ p → T ↑ q                                     p = q ④ T ↑ p → T ↑ q ∗ F ↑ r                            p = q ∗ r ⑤ T ↑ p → F ↑ q                                      p = q ⑥ F ↑ p → ( E ↑ q )                                  p = q ⑦ F ↑ p → N U M ↑ q                              p = q \begin{aligned}① S&→E_{↑q}@ANSWER_{↓r}\ \ \ \ \ \ \ \ r=q\\ ② E_{↑p}&→E_{↑q}+T_{↑r}\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ p=q+r\\ ③ E_{↑p}&→T_{↑q}\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ p=q\\ ④ T_{↑p}&→T_{↑q}*F_{↑r}\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ p=q*r\\ ⑤ T_{↑p}&→F_{↑q}\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ p=q\\ ⑥ F_{↑p}&→(E_{↑q})\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ p=q\\ ⑦ F_{↑p}&→NUM_{↑q}\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ p=q\end{aligned} ①S②E↑p​③E↑p​④T↑p​⑤T↑p​⑥F↑p​⑦F↑p​​→E↑q​@ANSWER↓r​        r=q→E↑q​+T↑r​                        p=q+r→T↑q​                                   p=q→T↑q​∗F↑r​                          p=q∗r→F↑q​                                    p=q→(E↑q​)                                p=q→NUM↑q​                            p=q​

  • 动作符号 @ A N S W E R ANSWER ANSWER 的动作是输出表达式的计算结果
  • 下面给出 N U M ↑ 3 + N U M ↑ 2 ∗ N U M ↑ 3 NUM_{↑3}+ NUM_{↑2}* NUM_{↑3} NUM↑3​+NUM↑2​∗NUM↑3​ 的分析树
    • N U M ↑ 3 NUM_{↑3} NUM↑3​ 表示 N U M NUM NUM 有一个综合属性 3 3 3 代表它的数值, 3 3 3 是在词法分析阶段求出来的
      语法制导翻译和中间代码生成

继承属性

  • 继承属性的求值规则
    • 对产生式左部的非终结符,其继承属性继承前面产生式中该符号已有的继承属性值
    • 对产生式右部的符号,其继承属性由产生式中其它符号属性值进行计算


S → E ↑ q @ A N S W E R ↓ r , r = q S→E_{↑q}@ANSWER_{↓r} , r=q S→E↑q​@ANSWER↓r​,r=q

  • 其中动作符号 @ A N S W E R ANSWER ANSWER 的属性来源于左边非终结符号 E E E 的属性,我们用一个向下的箭头表示该动作符号的属性值,这就是继承属性的一个例子


考虑下列声明语句文法
① < 声 明 语 句 > → T Y P E     I D < 变 量 表 > ; ② < 变 量 表 > → , I D < 变 量 表 > ③ < 变 量 表 > → ε ① <声明语句>→TYPE\ \ \ ID <变量表>;\\ ② <变量表>→,ID <变量表>\\ ③ <变量表>→ε ①<声明语句>→TYPE   ID<变量表>;②<变量表>→,ID<变量表>③<变量表>→ε

  • 假设每个变量具有 类型 两个属性
  • 词法分析程序处理 I N T   V ; INT\ V; INT V; 后返回的单词属性字:
    ( T Y P E      i n t ) , ( I D      V ) , ( ; ) (TYPE \ \ \ \ int),(ID \ \ \ \ V),(;) (TYPE    int),(ID    V),(;)
  • 语法分析程序在处理声明语句 I N T   V ; INT\ V; INT V; 时,假定调用 S E T _ T Y P E SET\_TYPE SET_TYPE 过程。该过程根据 T Y P E TYPE TYPE 的属性确定变量的类型,并将类型与词法分析程序得到的变量名 ( i d . n a m e id.name id.name) 一同插入到符号表中
  • 翻译文法如下:
    ① < 声 明 语 句 > → T Y P E     I D @ S E T _ T Y P E < 变 量 表 > ; ② < 变 量 表 > → , I D @ S E T _ T Y P E < 变 量 表 > ③ < 变 量 表 > → ε ① <声明语句>→TYPE\ \ \ ID @SET\_TYPE <变量表>;\\ ② <变量表>→,ID@SET\_TYPE <变量表>\\ ③ <变量表>→ε ①<声明语句>→TYPE   ID@SET_TYPE<变量表>;②<变量表>→,ID@SET_TYPE<变量表>③<变量表>→ε
    • 从文法上看,动作符号 @ S E T _ T Y P E @SET\_TYPE @SET_TYPE 有两个属性: @ S E T _ T Y P E ↓ 变 量 名 , 类 型 @SET\_TYPE_{↓变量名,类型} @SET_TYPE↓变量名,类型​
  • 属性变量表示符号的属性,对第1个产生式, T Y P E TYPE TYPE 和 I D ID ID 的属性值可由词法分析程序的返回值得到。定义属性文法如下:
    ① < 声 明 语 句 > → T Y P E ↑ t I D ↑ n @ S E T _ T Y P E ↓ n 1 , t 1 < 变 量 表 ↓ t 2 > ; ② < 变 量 表 ↓ t > → , I D ↑ n @ S E T _ T Y P E ↓ n 1 , t 1 < 变 量 表 ↓ t 2 > ① <声明语句>→TYPE_{↑t} ID_{↑n} @SET\_TYPE_{↓n_1,t_1} <变量表↓_{t_2} >; \\②<变量表_{↓t}>→,ID_{↑n} @SET\_TYPE_{↓n_1,t_1}<变量表_{↓t_2}> ①<声明语句>→TYPE↑t​ID↑n​@SET_TYPE↓n1​,t1​​<变量表↓t2​​>;②<变量表↓t​>→,ID↑n​@SET_TYPE↓n1​,t1​​<变量表↓t2​​>属性求值规则均为:
    t 2 = t , t 1 = t , n 1 = n t_2=t,t_1=t ,n_1=n t2​=t,t1​=t,n1​=n


如果输入符号串为 i n t   a , b ; int\ a, b; int a,b;

  • 词法分析后输出为:
    ( T Y P E      i n t ) , ( I D      a ) , ( , ) , ( I D      b ) , ( ; ) (TYPE \ \ \ \ int),(ID \ \ \ \ a),(,),(ID \ \ \ \ b),(;) (TYPE    int),(ID    a),(,),(ID    b),(;)
  • 语法分析要分析的输入串是: T Y P E   I D , I D ; TYPE\ ID ,ID ; TYPE ID,ID;
    • 带有属性的输入串是: T Y P E ↑ i n t I D ↑ a , I D ↑ b ; TYPE_{↑int} ID_{↑a} ,ID_{↑b} ; TYPE↑int​ID↑a​,ID↑b​;
    • 语法树如下图
      语法制导翻译和中间代码生成

属性文法举例—算术表达式的翻译

  • 假定一个属性翻译文法的输出是四元式代码。要求翻译程序产生的四元式具有下面的性质:每个双目运算都用一个四元式表示
    • 一组四元式的排列顺序与执行时要完成的运算顺序相同。
    • 每个四元式有三个参数,自左向右的顺序为:左操作数,右操作数,运算结果
    • 例如:翻译器处理表达式 a + a ∗ b a+a*b a+a∗b,经词法分析后应为 I D ↑ a + I D ↑ a ∗ I D ↑ b ID_{↑a}+ID_{↑a} * ID_{↑b} ID↑a​+ID↑a​∗ID↑b​,将生成如下的四元式
      ( ∗ , a , b , t 1 ) ( + , a , t 1 , t 2 ) (* , a , b , t_1 )\\( +, a , t_1 , t_2) (∗,a,b,t1​)(+,a,t1​,t2​)其中 t 1 , t 2 t_1,t_2 t1​,t2​ 是临时变量,保存表达式的结果

  • 第 1 步,构造表达式的翻译文法如下:
    E → E + T @ A D D E → T T → T ∗ F @ M U L T T → F F → ( E ) F → I D \begin{aligned}E&→E+T@ADD \\ E&→T\\ T&→T*F@MULT\\ T&→F\\ F&→(E)\\ F&→ID\end{aligned} EETTFF​→E+T@ADD→T→T∗F@MULT→F→(E)→ID​
    • @ A D D @ADD @ADD 代表产生加法运算的四元式
    • @ M U L T @MULT @MULT 代表产生乘法运算的四元式
  • 第 2 步:构造属性和求值规则,把翻译文法构造成属性翻译文法
    • 令每个非终结符有一个综合属性,该属性为一个临时变量,保存由它产生的表达式的结果
    • 输入符号 I D ID ID 有一个综合属性,它是该符号的变量名
    • 每个动作符号有三个继承属性,它们分别指向左操作数、右操作数和运算结果
      E ↑ x → E ↑ q + T ↑ r @ A D D ↓ y , z , t    y = q , z = r , t = N E W T , x = t E ↑ x → T ↑ p                                    x = p T ↑ x → T ↑ q ∗ F ↑ r @ M U L T ↓ y , z , t    y = q , z = r , t = N E W T , x = t T ↑ x → F ↑ p                                    x = p F ↑ x → ( E ↑ p )                              x = p F ↑ x → I D ↑ p                                  x = p \begin{aligned}E_{↑x} &→E_{↑q} +T_{↑r} @ADD_{↓y,z,t}\ \ y=q, z=r, t=NEWT,x=t \\ E_{↑x} &→T_{↑p} \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ x=p\\ T_{↑x}&→T_{↑q} *F_{↑r} @MULT_{↓y,z,t}\ \ y=q, z=r, t=NEWT,x=t\\ T_{↑x}&→F_{↑p}\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ x=p\\ F_{↑x}&→(E_{↑p})\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ x=p\\ F_{↑x}&→ID_{↑p}\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ x=p\end{aligned} E↑x​E↑x​T↑x​T↑x​F↑x​F↑x​​→E↑q​+T↑r​@ADD↓y,z,t​  y=q,z=r,t=NEWT,x=t→T↑p​                                  x=p→T↑q​∗F↑r​@MULT↓y,z,t​  y=q,z=r,t=NEWT,x=t→F↑p​                                  x=p→(E↑p​)                            x=p→ID↑p​                                x=p​
      • 其中, N E W T NEWT NEWT 是一个函数,每次调用它时返回一个新的临时变量名,临时变量名按产生顺序分别为 t 1 、 t 2 、 … t_1、t_2、… t1​、t2​、…
      • 动作符号 @ A D D ↓ y , z , t @ADD_{↓y,z,t} @ADD↓y,z,t​ 输出 ( + , y , z , t ) (+, y,z,t) (+,y,z,t)
      • 动作符号 @ M U L T ↓ y , z , t @MULT_{↓y,z,t} @MULT↓y,z,t​ 输出 ( ∗ , y , z , t ) (*, y,z,t) (∗,y,z,t)
        语法制导翻译和中间代码生成

S S S 属性文法的自下而上翻译

  • 针对任意一个语法制导定义的翻译器可能很难实现,但是有一大类的语法制导定义的翻译器很容易建立
  • 首先,我们考虑这样一类语法制导定义 S S S 属性定义,它的文法符号仅含综合属性
    • 综合属性可以在分析输入串的同时由自下而上的语法分析器来计算,这种语法分析器可以将文法符号的综合属性值保存在栈中,每当进行归约时,新的综合属性值就由栈中正在归约的产生式右部符号的属性值来计算
    • S S S 属性定义的翻译器可以借助 LR 语法分析器及生成器来实现。在自下而上的分析中,我们在原状态栈、文法符号栈的基础上增加一个属性栈,用来存放综合属性值,它保存已经分析过的子树的信息;这样,LR 分析器不仅执行语法分析任务,且在归约同时完成上例属性文法描述的语义动作
      语法制导翻译和中间代码生成


如下的算术表达式的属性文法定义是 S S S 属性文法
① S → E ↑ x ② E ↑ x → E ↑ q + T ↑ r @ A D D ↓ q , r , x                x = N W E V ③ E ↑ x → T ↑ x ④ T ↑ x → T ↑ q ∗ F ↑ r @ M U L T ↓ q , r , x                x = N W E V ⑤ T ↑ x → F ↑ x ⑥ F ↑ x → ( E ↑ x ) ⑦ F ↑ x → i ↑ x \begin{aligned}① S&→E_{↑x}\\ ② E_{↑x}&→E_{↑q}+T_{↑r}@ADD_{↓q,r,x}\ \ \ \ \ \ \ \ \ \ \ \ \ \ x=NWEV\\ ③ E_{↑x}&→T_{↑x}\\ ④ T_{↑x}&→T_{↑q}*F_{↑r}@MULT_{↓q,r,x}\ \ \ \ \ \ \ \ \ \ \ \ \ \ x=NWEV\\ ⑤ T_{↑x}&→F_{↑x}\\ ⑥ F_{↑x}&→(E_{↑x})\\ ⑦ F_{↑x}&→i_{↑x}\end{aligned} ①S②E↑x​③E↑x​④T↑x​⑤T↑x​⑥F↑x​⑦F↑x​​→E↑x​→E↑q​+T↑r​@ADD↓q,r,x​              x=NWEV→T↑x​→T↑q​∗F↑r​@MULT↓q,r,x​              x=NWEV→F↑x​→(E↑x​)→i↑x​​


给定一个 S S S 属性翻译文法,如何构造翻译器?

  • 我们将 S S S 属性翻译文法中的属性和动作符号去掉,形成输入文法。为输入文法构造 LR 分析表
  • 确定文法中每个符号的栈符号,并扩充该分析表的移进和归约动作,即可完成 S S S 属性翻译文法翻译器的构造
    • S S S 属性翻译文法的翻译器中,每个栈符号由名字部分和属性域组成。栈中任何符号的域都是一些存贮单元,当该符号在栈内时,这些域用于保存该符号的属性信息
    • 为了实现自底向上的属性文法的翻译,还需要对相应分析器的移进和归约动作做适当的扩充
      • 移进动作的扩充方案把当前输入符号的属性放在移进操作压入的那个栈符号的相应属性域中
      • 归约动作的扩充方案:当选用产生式 p p p 进行归约操作时,使用符号栈顶要归约的产生式右部符号的属性来计算与该产生式有关的所有动作符号以及左部非终结符号的所有属性
        • 使用这些动作符号属性来产生所需要的输出或完成有关的动作
        • 使用左部非终结符属性来填写表示左部非终结符的属性域,同时归约操作把这左部非终结符压入栈中
          语法制导翻译和中间代码生成

L L L 属性文法的自上而下翻译

  • 属性文法由翻译文法和有关的属性计算规则组成。如果属性计算规则给的不当,就不能保证所有的属性计算出来
  • L L L 属性文法 保证可以按照自顶向下的有序方式来计算属性值,即按照自顶向下的有序方式对某个属性求值时,所需要的基本值已知
  • L L L 属性文法定义
    • 一个语法制导定义是 L L L 属性定义,如果对于属性文法 A G AG AG 中的任一产生式 A → X 1 X 2 … X i … . X n A→X_1X_2…X_i…. X_n A→X1​X2​…Xi​….Xn​, 其每个语义规则中的每一个属性都是一个综合属性,或是 X i X_i Xi​( 1 ≤ i ≤ n 1≤ i≤n 1≤i≤n) 的一个继承属性,这一继承属性只依赖于 A A A 的继承属性和 X i X_i Xi​ 的左边符号 X 1 , X 2 , … , X i − 1 X_1,X_2,…,X_{i-1} X1​,X2​,…,Xi−1​ 的属性

L L L 属性中的 L L L 表示 l e f t left left

  • L L L 属性文法满足如下的条件:
    • X i X_i Xi​ 的继承属性只依赖于 A A A 的继承属性和 X i X_i Xi​ 的左边符号 X 1 , X 2 , … , X i − 1 X_1,X_2,…,X_{i-1} X1​,X2​,…,Xi−1​ 的属性
    • 该条件有利于自顶向下地对属性求值。因为每个符号都是在它右边的输入符号读入之前进行处理
  • A A A 的综合属性只依赖于 A A A 的继承属性和产生式右部符号的属性
    • 该条件保证在求值过程中避免出现循环依赖性


< 声 明 语 句 > → T Y P E ↑ t I D ↑ n @ S E T _ T Y P E ↓ n 1 , t 1 < 变 量 表 ↓ t 2 > ; <声明语句>→TYPE_{↑t} ID_{↑n} @SET\_TYPE_{↓n_1,t_1} <变量表↓_{t_2} >; <声明语句>→TYPE↑t​ID↑n​@SET_TYPE↓n1​,t1​​<变量表↓t2​​>;属性求值规则: t 2 = t , t 1 = t , n 1 = n t_2=t,t_1=t ,n_1=n t2​=t,t1​=t,n1​=n

** L L L 属性文法翻译的实现——递归下降翻译

可采用递归下降法,完成 L L L 属性文法的翻译。步骤如下:

  1. 若该非终结符具有属性,那么该非终结符的分析过程就有形参,且形参的数目就是该非终结符的属性个数
  2. 对于继承属性,采用值形参的传参方式将继承属性值传入被调过程
  3. 对于综合属性,采用变量形参的传参方式以便将值回传给主调过程 (指针 / 引用)

为了进行属性翻译的程序设计,我们采用下述约定:

  • 可以把属性产生式中的属性名字用作变量和参数的名字。这样可以将属性的命名和递归下降过程的实现联系起来
  • 所有出现在左部的同名非终结符,应具有相同的属性名表。在左部同名非终结符属性名表的同一化过程中,属性名称的改动范围仅局限于产生式左部。为了保证一致性,左部属性重新命名以后,可使用新的记法约定来简化或删去某些属性求值规则
  • 如果两个属性有相同的值,那么可给它们相同的名字,但对于左部符号的属性值相等时,不能改变成相同的名字

  • 产生式 L ↑ a ↓ b → e ↓ i R ↓ j , i , j = b , a = i + 2 L ↑ x ↓ y → H ↑ z ↓ w , w = y , z = 2 , x = z + y L_{↑a↓b}→e_{↓i} R_{↓j} , i, j = b, a = i+2\\ L_{↑x↓y}→H_{↑z↓w}, w = y, z = 2, x = z+y L↑a↓b​→e↓i​R↓j​,i,j=b,a=i+2L↑x↓y​→H↑z↓w​,w=y,z=2,x=z+y按约定第②条,必须改成
    L ↑ a ↓ b → e ↓ i R ↓ j , i , j = b , a = i + 2 L ↑ a ↓ b → H ↑ z ↓ w , w = b , z = 2 , x = z + b L_{↑a↓b}→e_{↓i} R_{↓j} , i, j = b, a = i+2\\ L_{↑\boldsymbol{a}↓\boldsymbol{b}}→H_{↑z↓w}, w = \boldsymbol{b}, z = 2, x = z+\boldsymbol{b} L↑a↓b​→e↓i​R↓j​,i,j=b,a=i+2L↑a↓b​→H↑z↓w​,w=b,z=2,x=z+b
  • 对规则 S → A ↑ a B ↓ b C ↓ c S→A_{↑a} B_{↓b} C_{↓c} S→A↑a​B↓b​C↓c​ ,当 b , c = a b, c=a b,c=a 时,可写成 S → A ↑ a B ↓ a C ↓ a S→A_{↑a} B_{↓a} C_{↓a} S→A↑a​B↓a​C↓a​
  • 对规则 L ↑ a → A ↓ b @ f ↓ c L_{↑a} →A_{↓b} @f_{↓c} L↑a​→A↓b​@f↓c​ ,当 a = b , c = b a=b, c=b a=b,c=b 时,可写成 L ↑ a → A ↓ a @ f ↓ a L_{↑a} →A_{↓a} @f_{↓a} L↑a​→A↓a​@f↓a​
  • 对规则 L ↓ a ↑ b → a B ↓ c C ↓ d L_{↓a↑b} →aB_{↓c}C_{↓d} L↓a↑b​→aB↓c​C↓d​ ,当 c , d = a c, d=a c,d=a 时,可写成 L ↓ a ↑ b → a B ↓ a C ↓ a L_{↓a↑b} →aB_{↓a}C_{↓a} L↓a↑b​→aB↓a​C↓a​,但当 b = a b=a b=a 时,不能写成 L ↓ a ↑ a → a B ↓ a C ↓ a L_{↓a↑a} →aB_{↓a}C_{↓a} L↓a↑a​→aB↓a​C↓a​
    • 这是因为左部非终结符号的属性将作为该非终结符号分析过程的形参,而一个过程的形参不能重名,如过程 L ( i n t   a , i n t   b ) L(int\ a, int\ b) L(int a,int b) 不可写成 L ( i n t   a , i n t   a ) L(int\ a,int\ a) L(int a,int a)

中间代码的形式

逆波兰表示法

  • 逆波兰表示法 (后缀式) 的递归定义:
    • 如果 E E E 是一个变量或常量,则 E E E 的后缀表示是 E E E 本身
    • 如果 E E E 是一个形如 E 1   o p   E 2 E_1\ op\ E_2 E1​ op E2​ 的表达式,其中 o p op op 是一个二目运算符, 那么 E E E 的后缀表示是 E 1 ′   E 2 ′   o p E_1'\ E_2'\ op E1′​ E2′​ op, 这里 E 1 ′ E_1' E1′​ 和 E 2 ′ E_2' E2′​ 分别是 E 1 E_1 E1​ 和 E 2 E_2 E2​ 的后缀表示
    • 如果 E E E 是一个形如 ( E 1 ) (E_1) (E1​) 的被括号括起来的表达式,则 E E E 的后缀表示就是 E 1 E_1 E1​ 的后缀表示
  • 表达式的逆波兰式表示的实质:
    • 操作数出现的顺序与原来顺序一致,而运算符则按运算的先后顺序放在相应的操作数之后(又称后缀表示)。这种表示不需要用括号来规定运算的顺序
    • 易于计算机处理表达式,特别适用于解释执行的程序设计语言的中间表示,也方便具有堆栈体系的计算机的目标代码生成
  • 扩充逆波兰式
    • 赋值表达式: < 变 量 > : = < 表 达 式 > <变量>:=<表达式> <变量>:=<表达式>;逆波兰式表示: < 变 量 > < 表 达 式 > : = <变量><表达式>:= <变量><表达式>:=
    • x = − a + b x=-a+b x=−a+b;逆波兰式表示: x a @ b + = xa@b+= xa@b+= (@代表取负运算)
    • g o t o   L 1 goto\ L_1 goto L1​;逆波兰式表示: L 1   J P L_1\ JP L1​ JP (JP是单目运算符)
    • 若用 ? ? ? 表示 i f if if- t h e n then then- e l s e else else, 则
      I f   a   t h e n   i f   c − d   t h e n   a + c   e l s e   a ∗ c   e l s e   a + b If\ a\ then\ if\ c-d\ then\ a+c\ else\ a*c\ else\ a+b If a then if c−d then a+c else a∗c else a+b逆波兰式表示
      a c d − a c + a c ∗ ? a b + ? a cd- ac+ ac*? ab+? acd−ac+ac∗?ab+?
      • 然而,这种 i f if if- t h e n then then- e l s e else else 运算符的实现 e x y ? exy? exy?,要求在任何情况下都要把 x , y x,y x,y 都计算出来,没有短路求值的特性。而且,如果运算量无定义或者有副作用,则后缀表示法不仅无效,而且可能是错误的
      • 解决方案:
        • 引入标号,在后缀式中加入条件转移,无条件转移算符
        • 存储方式:后缀式存放在一维数组 P O S T [ 1.. N ] POST[1..N] POST[1..N] 中,每个元素是运算符或者分量(指向符号表)
        • p   j u m p p\ jump p jump:转到 P O S T [ p ] POST[p] POST[p]
        • e 1 e 2 p   j l t e_1e_2p\ jlt e1​e2​p jlt: e 1 < e 2 e_1<e_2 e1​<e2​ 时,转到 P O S T [ p ] POST[p] POST[p]
        • e p   j e z ep\ jez ep jez:若 e = 0 e=0 e=0,转到 P O S T [ p ] POST[p] POST[p]
        • I f   e   t h e n   x   e l s e   y If\ e\ then\ x\ else\ y If e then x else y 的逆波兰式表示: e p 1   j e z   x p 2   j u m p   p 1 : y ep_1\ jez\ xp_2\ jump\ p_1:y ep1​ jez xp2​ jump p1​:y
          语法制导翻译和中间代码生成
  • 产生式所带的语义动作,由以下模式描述:
    • E → E ( 1 ) o p E ( 2 )        { E . C O D E : = E ( 1 ) . C O D E ∣ ∣ E ( 2 ) . C O D E ∣ ∣ o p } / { p r i n t   o p } E→E^{(1)}op E^{(2)}\ \ \ \ \ \ \{ E.CODE:= E^{(1)}.CODE || E^{(2)}.CODE||op \}/ \{print\ op\} E→E(1)opE(2)      {E.CODE:=E(1).CODE∣∣E(2).CODE∣∣op}/{print op}
    • E → ( E ( 1 ) )        { E . C O D E : = E ( 1 ) . C O D E } / { } E→(E^{(1)})\ \ \ \ \ \ \{ E.CODE := E^{(1)}.CODE\} / \{\} E→(E(1))      {E.CODE:=E(1).CODE}/{}
    • E → i d        { E . C O D E : = i d } / { p r i n t   i d } E→id\ \ \ \ \ \ \{ E.CODE := id\} / \{print\ id\} E→id      {E.CODE:=id}/{print id}

三元式

  • 三元式的构成: O P   O P 1   O P 2 OP\ OP_1\ OP_2 OP OP1​ OP2​
    • O P 1 OP_1 OP1​ 和 O P 2 OP_2 OP2​ 都是指示器,指向符号表的某项(符号表入口),或者三元式表自身的某项
    • O P OP OP 为运算符,通常用整数编码
    • 三元式含有对中间结果的显示引用(计算结果与所加编号联系)
      语法制导翻译和中间代码生成
  • 产生式所带的语义动作,由以下模式描述
    • E → E ( 1 )   o p   E ( 2 )      { E . V A L : = T R I P ( o p , E ( 1 ) . V A L , E ( 2 ) . V A L } E→E^{(1)}\ op\ E^{(2)}\ \ \ \ \{E.VAL:= TRIP(op,E^{(1)}.VAL,E^{(2)}.VAL\} E→E(1) op E(2)    {E.VAL:=TRIP(op,E(1).VAL,E(2).VAL}
    • E → ( E ( 1 ) )      { E . V A L : = E ( 1 ) . V A L } E→(E^{(1)})\ \ \ \ \{E.VAL:= E^{(1)}.VAL\} E→(E(1))    {E.VAL:=E(1).VAL}
    • E → − E ( 1 )      { E . V A L : = T R I P ( @ , E ( 1 ) . V A L , − ) } E→-E^{(1)}\ \ \ \ \{E.VAL:= TRIP(@,E^{(1)}.VAL,-)\} E→−E(1)    {E.VAL:=TRIP(@,E(1).VAL,−)}
    • E → i d      { E . V A L : = E N T R Y ( i d ) } E→id\ \ \ \ \{E.VAL:= ENTRY(id)\} E→id    {E.VAL:=ENTRY(id)}
  • E . V A L E.VAL E.VAL:指示器,指向符号表中一项,或者三元式表中某一项
  • T R I P TRIP TRIP:语义过程,产生新的三元式,回送新三元式在三元式表中的位置
  • E N T R Y ENTRY ENTRY:对 i d id id 代表的标示符查找符号表以获知在表中的位置的函数;涉及2个语义过程:
    • L o o k u p ( N a m e ) Lookup(Name) Lookup(Name):对 N a m e Name Name 查符号表。查到则返回入口值,否则返回 N u l l Null Null (出错处理,或调用 F i l l s y m Fillsym Fillsym)
    • F i l l s y m ( N a m e ) Fillsym(Name) Fillsym(Name) :在符号表中开辟新项目,返回入口值

间接三元式

三元式的不足:

  • 若考虑代码优化问题,三元组表示并不适宜。因为代码优化时,通常需要删除某些运算,或者改变运算的次序,这势必要导致代码的移动
  • 三元式间的相互引用非常频繁,每当移动一个三元式时,就必须改变所有引用它的三元式。当需要进行代码优化时,三元组不是一种很好的中间语言形式。三元式中指示器连接,不能更动,不利于优化

间接三元式

  • 为了克服三元组表示不便于优化的缺点,可使用一张额外的执行表保存三元组的执行顺序。当需要完成优化时,只改变执行表中的项,而实际的三元组保持不变
  • 存放三元式本身的表,称为三元式表如果有两个相同的三元式,只需保留一个
  • 因此,对于间接三元式表示,产生三元式表时,应增添产生间接码表的语义动作。并且,在向三元式表填进一个三元式之前,必须先查看一下此式是否已在其中,如已在其中,就无需再填如

X : = ( A + B ) ∗ C Y : = D ↑ ( A + B ) X:=(A+B)*C\\ Y:=D↑(A+B) X:=(A+B)∗CY:=D↑(A+B)
语法制导翻译和中间代码生成

树形表示

  • 树形表示是三元式的翻版
    • 运算符为根,树的中序遍历为原表达式
    • 开始简单变量或者常数的树就是该变量或者常数自身。一般叶子表示运算量,内结点表示 O P OP OP
      • 如 e 1 e_1 e1​ 和 e 2 e_2 e2​ 的树为 T 1 T_1 T1​ 和 T 2 T_2 T2​, 则 e 1 + e 2 e_1+e_2 e1​+e2​、 e 1 ∗ e 2 e_1*e_2 e1​∗e2​ 和 − e 1 -e_1 −e1​ 的树分别为:
        语法制导翻译和中间代码生成

  • 产生式所带的语义动作,与三元式相似,由以下模式描述:
    • E → E ( 1 )   o p   E ( 2 )      { E . V A L : = N O D E ( o p , E ( 1 ) . V A L , E ( 2 ) . V A L } E→E^{(1)}\ op\ E^{(2)}\ \ \ \ \{E.VAL:= NODE(op,E^{(1)}.VAL,E^{(2)}.VAL\} E→E(1) op E(2)    {E.VAL:=NODE(op,E(1).VAL,E(2).VAL}
    • E → ( E ( 1 ) )      { E . V A L : = E ( 1 ) . V A L } E→(E^{(1)})\ \ \ \ \{E.VAL:= E^{(1)}.VAL\} E→(E(1))    {E.VAL:=E(1).VAL}
    • E → − E ( 1 )      { E . V A L : = U N A R Y ( @ , E ( 1 ) . V A L , − ) } E→-E^{(1)}\ \ \ \ \{E.VAL:= UNARY(@,E^{(1)}.VAL,-)\} E→−E(1)    {E.VAL:=UNARY(@,E(1).VAL,−)}
    • E → i d      { E . V A L : = L E A F ( i d ) } E→id\ \ \ \ \{E.VAL:= LEAF(id)\} E→id    {E.VAL:=LEAF(id)}
    • N O D E NODE NODE:建立一个以 O P OP OP 为结点, E ( 1 ) . V A L E^{(1)}.VAL E(1).VAL 和 E ( 2 ) . V A L E^{(2)}.VAL E(2).VAL 为左右枝的子树,回送新子树根的指针
    • U N A R Y UNARY UNARY:语义过程与 N O D E NODE NODE 相仿,但是它只有一个分枝
    • E N T R Y ENTRY ENTRY:建立一个以 i . L E X V A L i.LEXVAL i.LEXVAL 为标志的结点,并回送此结的地址,该结是个叶结点

例: A ∗ ( B + C ) / D A*(B+C)/D A∗(B+C)/D
语法制导翻译和中间代码生成


  • 当然,我们可以为任意的构造创建抽象语法树,而不仅仅为表达式创建语法树。每个构造用一个结点表示,其子结点代表此构造中具有语义含义的组成部分
    • 比如,
      w h i l e    ( e x p r )    s t m t while\ \ (expr)\ \ stmt while  (expr)  stmt中,具有语义含义的组成部分是表达式 e x p r expr expr 和语句 s t m t stmt stmt 。这样的 w h i l e while while 语句的抽象语法树结点有一个运算符 w h i l e while while, 并有两个子结点:分别是 e x p r expr expr 和 s t m t stmt stmt 的抽象语法树
    • 因此,类 N o d e Node Node 有两个直接子类,一个是 E x p r Expr Expr,代表各种表达式;另一个是 S t m t Stmt Stmt,代表各种语句。每一种语句都有一个对应的 S t m t Stmt Stmt 的子类。比如,运算符 w h i l e while while 对应子类 w h i l e while while。一个对应于运算符 w h i l e while while, 子结点为 x x x 和 y y y 的语法树结点可以由如下伪代码创建:
      n e w   W h i l e ( x , y ) ne w\ While(x,y) new While(x,y)
  • 在下面的表格中,属性 n n n 表示某个结点的子树;例如 s t m t → b l o c k       { s t m t . n = b l o c k . n } stmt\rightarrow block \ \ \ \ \ \{stmt.n=block.n\} stmt→block     {stmt.n=block.n} 就表示当一个语句是一个语句块时,它的抽象语法树和这个语句块的相同
    语法制导翻译和中间代码生成
    语法制导翻译和中间代码生成

四元式

  • 四元式格式: ( O P , O P 1 , O P 2 , R E S U L T ) (OP,OP_1,OP_2,RESULT) (OP,OP1​,OP2​,RESULT)
    • O P OP OP 代表算符; O P 1 、 O P 2 OP_1、OP_2 OP1​、OP2​ 代表运算对象, R E S U L T RESULT RESULT 存放运算结果
    • O P 1 、 O P 2 OP_1、OP_2 OP1​、OP2​ 和 R E S U L T RESULT RESULT 为符号表入口或者临时变量的整数码
    • R E S U L T RESULT RESULT 为 T i T_i Ti​ 时的处理:可以填入符号表,通过符号表入口进行引用,也可以不填,用某种整数编码代表它们
  • 扩充四元式
    • ( j p , _ , _ , L ) ( jp, \_, \_, L ) (jp,_,_,L), 也可写成 g o t o   L goto\ L goto L
    • ( j < , A , B , L ) ( j<, A, B, L) (j<,A,B,L), 也可写成 i f   A < B   g o t o   L if\ A <B\ goto\ L if A<B goto L
    • ( j n z , A , _ , L ) (jnz, A, \_, L) (jnz,A,_,L), 也写成 i f   A   g o t o   L if\ A\ goto\ L if A goto L

四元式特点

  • 四元组中四元式的顺序是按照相应表达式的实际运算顺序出现
  • 四元式使用临时变量计算和使用的联系不那么直接了,允许重排顺序,利于优化

: A : = − B ∗ ( C + D ) A:=-B*(C+D) A:=−B∗(C+D) 的四元式
( 1 ) ( @ , B , _ , T 1 ) ( 2 ) ( + , C , D , T 2 ) ( 3 ) ( ∗ , T 1 , T 2 , T 3 ) ( 4 ) ( : = , T 3 , _ , A ) (1)( @, B, \_ , T_1 )\\ (2)( +, C, D, T_2 )\\ (3)( *, T_1, T_2, T_3 )\\ (4)(:=, T_3, \_, A ) (1)(@,B,_,T1​)(2)(+,C,D,T2​)(3)(∗,T1​,T2​,T3​)(4)(:=,T3​,_,A)

: a : = b ∗ c + b ∗ d a:=b*c+b*d a:=b∗c+b∗d 的四元式
( 1 ) ( ∗ , b , c , t 1 ) ( 2 ) ( ∗ , b , d , t 2 ) ( 3 ) ( + , t 1 , t 2 , t 3 ) ( 4 ) ( : = , t 3 , _ , a ) (1)( *, b, c, t_1 )\\ (2)( *, b, d, t_2 )\\ (3)( +, t_1, t_2, t_3 )\\ (4)( :=, t_3 ,\_, a ) (1)(∗,b,c,t1​)(2)(∗,b,d,t2​)(3)(+,t1​,t2​,t3​)(4)(:=,t3​,_,a)

: i f if if E E E o r or or ( B < C ) (B<C) (B<C) t h e n then then S 1 S_1 S1​ e l s e else else S 2 S_2 S2​ 的四元式
( 1 ) ( j n z , E , _ , 5 ) ( 2 ) ( j , _ , _ , 3 ) ( 3 ) ( j < , B , C , 5 ) ( 4 ) ( j , _ , _ , p + 1 ) ( 5 ) 与 S 1 相 应 的 四 元 式 序 列 ( p ) ( j , _ , _ , q ) ( p + 1 ) 与 S 2 相 应 的 四 元 式 序 列 ( q ) … \begin{aligned}&(1)(jnz,E,\_,\boldsymbol5)\\ &(2)(j,\_,\_,3)\\ &(3)(j<,B, C,\boldsymbol5)\\ &(4)(j,\_,\_, p+1)\\ &(5) 与S_1相应的四元式序列\\ &(p)(j,\_, \_,q)\\ &(p+1)与 S_2 相应的四元式序列\\ &(q)…\end{aligned} ​(1)(jnz,E,_,5)(2)(j,_,_,3)(3)(j<,B,C,5)(4)(j,_,_,p+1)(5)与S1​相应的四元式序列(p)(j,_,_,q)(p+1)与S2​相应的四元式序列(q)…​

简单赋值语句的翻译

本节讨论只含整型变量的简单赋值句的翻译,其文法描述:
S → i : = E E → E + E ∣ E ∗ E ∣ − E ∣ ( E ) ∣ i S→i:=E\\ E→E+E|E*E|-E|(E)|i S→i:=EE→E+E∣E∗E∣−E∣(E)∣i

  • S S S 代表“赋值句”
  • 该文法是二义性文法,但通常假设算符的结合性和优先级的规定,二义性可以克服

语义变量与过程函数:

  • N E W T E M P NEWTEMP NEWTEMP: 每次调用时,它都回送一个代表新临时变量名的整数码作为函数值
  • E N T R Y ( i ) ENTRY(i) ENTRY(i):涉及2个语义过程:
    • L o o k u p ( i ) Lookup(i) Lookup(i):对 i i i 查符号表。查到则返回入口值,否则返回 N u l l Null Null (出错处理,或调用 F i l l s y m Fillsym Fillsym)
    • F i l l s y m ( i ) Fillsym(i) Fillsym(i) :在符号表中开辟新项目,返回入口值
  • E . P L A C E E.PLACE E.PLACE: 和非终结符 E E E 相联系的语义变量,表示存放 E E E 值的变量名在符号表的入口或者整数码(若为临时变量)
  • G E N ( O P , O P 1 , O P 2 , R E S U L T ) GEN(OP,OP_1,OP_2,RESULT) GEN(OP,OP1​,OP2​,RESULT): 语义过程,把四元式 ( O P , O P 1 , O P 2 , R E S U L T ) (OP,OP_1, OP_2,RESULT) (OP,OP1​,OP2​,RESULT) 填入四元式表

语义动作描述

语法制导翻译和中间代码生成
语法制导翻译和中间代码生成

A : = − B ∗ ( C + D ) A := - B * ( C + D ) A:=−B∗(C+D)

  • 归约树
    语法制导翻译和中间代码生成

类型转换

引入类型信息语义量

  • 在赋值语句翻译中,在进行混合运算时,首先要将整型量转换为实型量。而要进行转换,其前提是对每一 V N V_N VN​,必须有类型信息语义量 E . M O D E E.MODE E.MODE;对应的产生式要附加关于 E . M O D E E.MODE E.MODE 的语义规则;非终结符 E E E 的语义值 E . M O D E E.MODE E.MODE 必须保存在翻译栈中

语义规则
{ I F   E ( 1 ) . M O D E = i n t   A N D   E ( 2 ) . M O D E = i n t T H E N   E . M O D E : = i n t E L S E   E . M O D E : = r } \{ IF\ E^{(1)}.MODE = int\ AND\ E^{(2)}.MODE = int\\ THEN\ E.MODE := int\\ ELSE\ E.MODE := r\} {IF E(1).MODE=int AND E(2).MODE=intTHEN E.MODE:=intELSE E.MODE:=r}

类型转换四元式

  • 将整型量 A 1 A_1 A1​ 转换成实型量:
    ( i t r , A 1 , _ , T ) ( itr, A_1,\_,T) (itr,A1​,_,T)

  • 输入串为 X : = Y + I ∗ J X := Y + I * J X:=Y+I∗J, X , Y X, Y X,Y 为实型, I , J I, J I,J 为整型
  • 四元式如下:
    ( ∗ i , I , J , T 1 ) ( i t r , T 1 , _ , T 2 ) ( + r , Y , T 2 , T 3 ) ( : = , T 3 , _ , X ) ( *^i , I , J , T_1)\\ ( itr , T_1, \_ ,T_2)\\ ( +^r , Y , T_2 , T_3)\\ ( :=, T_3 , \_ , X) (∗i,I,J,T1​)(itr,T1​,_,T2​)(+r,Y,T2​,T3​)(:=,T3​,_,X)
    • 运算符要指出相称的类型,说明是定点还是浮点运算

布尔表达式的翻译

  • 布尔表达式是由布尔算符 ( ∧ , ∨ , ┐ ∧,∨,┐ ∧,∨,┐) 作用于布尔变量或关系表达式而形成的。形式为 E 1   r o p   E 2 E_1\ rop\ E_2 E1​ rop E2​;其中, r o p rop rop 是关系符, E 1 E_1 E1​ 和 E 2 E_2 E2​ 是算术式
  • 文法: E → E ∧ E ∣ E ∨ E ∣ ┐ E ∣ ( E ) ∣ i ∣ i   r o p   i E→E∧E | E∨E | ┐E | (E) | i | i\ rop\ i E→E∧E∣E∨E∣┐E∣(E)∣i∣i rop i
    • 布尔算符的优先顺序: ┐ , ∧ , ∨ , ∧ , ∨ ┐,∧,∨, ∧,∨ ┐,∧,∨,∧,∨ 服从左结合
    • 所有关系符的优先级相同,高于任何布尔算符,低于任何算术算符
    • 关系符不得结合,如 A > B > C A>B>C A>B>C 不合法

拉链回填


布尔表达式 a < b   o r   c > = d a < b\ or\ c >= d a<b or c>=d 的四元式
语法制导翻译和中间代码生成
但注意到,上面的翻译其实是不合理的,因为没有考虑短路求值的特性
语法制导翻译和中间代码生成


  • 布尔表达式 E E E 的翻译结构:
    • 从整体上, E E E 对外只能转向两个目标
      语法制导翻译和中间代码生成
    • E E E 的翻译是由一组条件真跳无条件跳代码组成。待填地址采用回填、拉链方法实现
    • E . t r u e E.true E.true:表示 E E E 的真链。把需要回填真出口的四元式拉成链,它们的真出口与 E E E 的真出口一致,用 E . t r u e E.true E.true 指向
    • E . f a l s e E.false E.false:表示 E E E 的假链。把需要回填假出口的四元式拉成链,它们的假出口与 E E E 的假出口一致,用 E . f a l s e E.false E.false 指向


I F   A ∨ B < D   T H E N   S 1   E L S E   S 2 IF\ A∨B<D\ THEN\ S_1\ ELSE\ S_2 IF A∨B<D THEN S1​ ELSE S2​
语法制导翻译和中间代码生成

  • 红色为真链;蓝色为假链
  • 上图中的红色的 5 是通过真链回填得到的,当还没有得到具体要跳转的地址时,跳转地址可以暂时由 0 (链尾) 或 链上的上一个四元组的标号
    语法制导翻译和中间代码生成

文法定义

  • 布尔表达式文法
    G 1 [ E ] : E → E ∧ E ∣ E ∨ E ∣ ¬ E ∣ ( E ) ∣ i ∣ i   r o p   i G_1[E]:E→E∧E∣E∨E|¬E|(E)|i|i\ rop\ i G1​[E]:E→E∧E∣E∨E∣¬E∣(E)∣i∣i rop i
  • 改造布尔表达式文法
    G 2 [ E ] : E → E   ^ E ∣ E ° E ∣ ¬ E ∣ ( E ) ∣ i ∣ i   r o p   I E   ^ → E ∧ E ° → E ∨ G_2[E]:E→E\ \hat{} E|E°E|¬E|(E)|i|i\ rop\ I\\ E\ \hat{}→E∧\\ E°→E∨ G2​[E]:E→E ^E∣E°E∣¬E∣(E)∣i∣i rop IE ^→E∧E°→E∨

至于为什么这么改造,看了下面的具体操作步骤就明白了;中心思想是因为在自底向上分析中,符号串归约出栈之后,它们的属性也会删除,因此需要在归约时保存相应的信息 (在这个例子中,这个信息就是 T C TC TC 和 F C FC FC)

当然,这样改造不是唯一的


语法制导翻译和中间代码生成

可以看到,每个语义动作中,如果产生式右部的 T C , F C TC,FC TC,FC 属性可以填写,就直接进行回填;如果目前还不能填,就由产生式左部的属性加以保存,在之后进行回填

  • 每一个非终结符号 E E E 都有两个综合属性 T C TC TC 和 F C FC FC 用来管理布尔表达式的跳转代码中的标号:
    • T C TC TC 为一个包含跳转或条件跳转指令的列表,我们必须向这些指令中插入适当的标号,也就是当 E E E 为真时控制流应该转向的标号
    • 类似地, F C FC FC 也是一个包含跳转指令的列表,这些指令最终获得的标号就是当 E E E 为假时控制流应该转向的标号
    • 在生成 E E E 的代码时,跳转到真或假出口的跳转指令是不完整的,标号字段尚未填写。这些不完整的跳转指令被保存在 E . T C E.TC E.TC 和 E . F C E.FC E.FC 所指的列表中
  • N X Q NXQ NXQ 表示下一个四元组的序号
  • G E N GEN GEN 产生一个四元组
  • B A C K P A T C H ( p , i ) BACKPATCH(p, i) BACKPATCH(p,i) 表示回填,将 i i i 作为目标标号插入到 p p p 所指列表中的各指令中
  • M E R G ( p 1 , p 2 ) MERG(p_1,p_2) MERG(p1​,p2​) 表示将 p 1 p_1 p1​ 和 p 2 p_2 p2​ 指向的列表进行合并,返回合并后的列表
    语法制导翻译和中间代码生成

3 3 3 为拉链回填的结果; 1 1 1 指向真链中的上一个四元组的位置

控制结构的翻译

标号和转移语句的翻译

  • 语言中允许标号先定义后使用,也允许先使用后定义
    L : S G o t o   L L: S\\ Goto\ L L:SGoto L
    • 先定义:
      语法制导翻译和中间代码生成
  • 先使用:
    语法制导翻译和中间代码生成

标号语句的产生式
S → l a b e l   S l a b e l → i : S →label\ S\\label→i: S→label Slabel→i:

  • L a b e l → i Label→i Label→i: 的语义动作
    • 若 i i i 所指的标识符 (假定为 L L L) 不在符号表中,则把它填入,置类型为“标号”,“定义否”为“已”,地址为 N X Q NXQ NXQ
    • 若 L L L 已在符号表中,但“类型”不为“标号”或者“定义否”为“已”,则报告出错
    • 若 L L L 已在符号表中,则把标志“未”改为“已”,然后,把地址栏中的链头(记为 q q q)取出,同时把 N X Q NXQ NXQ 填在其中,最后,执行 B A C K P A T C H ( q , N X Q ) BACKPATCH(q,NXQ) BACKPATCH(q,NXQ)

条件语句的翻译

i f if if

语法制导翻译和中间代码生成

  • 较为复杂的程序控制语句常常是嵌套的;如下例所示,在 S 2 S_2 S2​ 翻译之后,也不能确定其跳转地址,它要跨越 S 2 , S 3 S_2, S_3 S2​,S3​:
    语法制导翻译和中间代码生成

  • 解决方法:仿照 “拉链回填” 方法
    • 令每个非终结符 S ( L ) S(L) S(L) 附带一项语义值 S . C H A I N S.CHAIN S.CHAIN,它指出一条链的头,该链是所有期待翻译完 S S S 后回填目标的四元式组成的
    • 回填目标可能是 { S } \{S\} {S} 的下一条四元式,也可能不是。真正的回填,要在处理完 S S S 的外层后进行


i f if if a < b a<b a<b o r or or c > d c>d c>d a n d and and e > f e>f e>f t h e n then then S 1 S_1 S1​ e l s e else else S 2 S_2 S2​ 的翻译
语法制导翻译和中间代码生成

w h i l e while while

  • W H I L E WHILE WHILE E ( 1 ) E^{(1)} E(1) D O DO DO S ( 1 ) S^{(1)} S(1)
    • 由于语句的嵌套, W H I L E WHILE WHILE 翻译完了也未必知道假出口的转移目标,所以作为 S ( 1 ) . C H A I N S^{(1)}.CHAIN S(1).CHAIN 保留下来,以便伺机回填
      语法制导翻译和中间代码生成


将 w h i l e ( A < B ) while(A<B) while(A<B) d o do do i f if if ( C < D ) (C<D) (C<D) t h e n then then X : = Y + Z X:=Y+Z X:=Y+Z 翻译成四元式
语法制导翻译和中间代码生成

条件语句的文法

S → i f   E   t h e n   S ∣ i f   E   t h e n   S   e l s e   S ∣ w h i l e   E   d o   S ∣ b e g i n   L   e n d ∣ A L → L ; S ∣ S \begin{aligned} S \rightarrow& if\ E\ then\ S\\ &|if\ E\ then\ S\ else\ S \\ &|while\ E\ do\ S\\ &|begin\ L\ end\\ &|A\\ L\rightarrow& L; S\\ &|S\end{aligned} S→L→​if E then S∣if E then S else S∣while E do S∣begin L end∣AL;S∣S​

  • S S S:语句
  • L L L:语句串
  • A A A:赋值句
  • E E E:布尔表达式

  • 为了能及时回填有关四元式的转移目标,如同处理布尔表达式一样,需要对文法进行改写
    S → C   S ∣ T P   S ∣ W d   S ∣ b e g i n   L   e n d ∣ A L → L S   S ∣ S C → i f   E   t h e n T P → C   S   e l s e W d → W   E   d o W → w h i l e L S → L ; \begin{aligned} S \rightarrow& C\ S\\ &| T^P\ S\\ &| W^d\ S\\ &| begin\ L\ end\\ &| A\\ L \rightarrow&L^S\ S\\ &| S\\ C\rightarrow&if\ E\ then\\ T^P\rightarrow&C\ S\ else\\ W^d\rightarrow&W\ E\ do\\ W \rightarrow&while\\ L^S\rightarrow&L;\end{aligned} S→L→C→TP→Wd→W→LS→​C S∣TP S∣Wd S∣begin L end∣ALS S∣Sif E thenC S elseW E dowhileL;​

条件语句的语义动作

C → i f   E   t h e n { B A C K P A T C H ( E . T C , N X Q ) ; C . C H A I N : = E . F C } S → C   S ( 1 ) { S . C H A I N : = M E R G ( C . C H A I N , S ( 1 ) . C H A I N ) } T P → C   S ( 1 )   e l s e { q : = N X Q ; G E N ( j , _ , _ , 0 ) ; B A C K P A T C H ( C . C H A I N , N X Q ) ; T P . C H A I N : = M E R G E ( S ( 1 ) . C H A I N , q ) } \begin{aligned} C \rightarrow&if\ E\ then\\ \{ &BACKPATCH (E.TC, NXQ);\\ &C.CHAIN := E.FC \}\\ S \rightarrow&C\ S^{(1)}\\ \{&S.CHAIN := MERG(C.CHAIN, S^{(1)}.CHAIN)\}\\ T^P \rightarrow&C\ S^{(1)}\ else\\ \{ &q := NXQ;\\ &GEN(j, \_, \_, 0);\\ &BACKPATCH(C.CHAIN,NXQ);\\ &T^P.CHAIN := MERGE(S^{(1)}.CHAIN,q)\}\\\end{aligned} C→{S→{TP→{​if E thenBACKPATCH(E.TC,NXQ);C.CHAIN:=E.FC}C S(1)S.CHAIN:=MERG(C.CHAIN,S(1).CHAIN)}C S(1) elseq:=NXQ;GEN(j,_,_,0);BACKPATCH(C.CHAIN,NXQ);TP.CHAIN:=MERGE(S(1).CHAIN,q)}​

注意,上面的 q q q 为 G E N ( j , _ , _ , 0 ) GEN(j, \_, \_, 0) GEN(j,_,_,0) 的位置;而第三行的 N X Q NXQ NXQ 指的是 G E N ( j , _ , _ , 0 ) GEN(j, \_, \_, 0) GEN(j,_,_,0) 的下一个四元式的地址

S → T P   S ( 2 ) { S . C H I A N : = M E R G ( T P . C H I A N , S ( 2 ) . C H I A N ) } \begin{aligned} S\rightarrow&T^P\ S^{(2)}\\ \{ &S.CHIAN := MERG(T^P.CHIAN, S^{(2)}.CHIAN)\}\end{aligned} S→{​TP S(2)S.CHIAN:=MERG(TP.CHIAN,S(2).CHIAN)}​


W → w h i l e { W . Q U A D : = N X Q } \begin{aligned} W \rightarrow&while\\ \{ &W.QUAD := NXQ \}\end{aligned} W→{​whileW.QUAD:=NXQ}​

w h i l e while while 归约为 W W W 是因为要记录 d o do do 中循环体做完之后的跳回位置; W . Q U A D W.QUAD W.QUAD 即为循环体做完判断 E E E 的地方

W d → W   E   d o { B A C K P A T C H ( E . T C , N X Q ) ; W d . C H A I N : = E . F C ; W d . Q U A D : = W . Q U A D } S → W d S ( 1 ) { B A C H P A T C H ( S ( 1 ) . C H A I N , W d . Q U A D ) ; G E N ( j , _ , _ , W d . Q U A D ) ; S . C H A I N : = W d . C H A I N } \begin{aligned} W^d \rightarrow&W\ E\ do\\ \{ &BACKPATCH (E.TC,NXQ);\\ &W^d.CHAIN := E.FC;\\ &W^d.QUAD := W.QUAD \}\\ S \rightarrow&W^d S^{(1)}\\ \{ &BACHPATCH(S^{(1)}.CHAIN,W^d.QUAD);\\ &GEN(j,\_,\_, W^d.QUAD); \\ &S.CHAIN := W^d.CHAIN\}\end{aligned} Wd→{S→{​W E doBACKPATCH(E.TC,NXQ);Wd.CHAIN:=E.FC;Wd.QUAD:=W.QUAD}WdS(1)BACHPATCH(S(1).CHAIN,Wd.QUAD);GEN(j,_,_,Wd.QUAD);S.CHAIN:=Wd.CHAIN}​


L → S { L . C H A I N : = S . C H A I N } L S → L ; { B A C K P A T C H ( L . C H A I N , N X Q ) } L → L S   S ( 1 ) { L . C H A I N : = S ( 1 ) . C H A I N } S → b e g i n   L   e n d { S . C H A I N : = L . C H A I N } S → A { S . C H A I N : = 0 } 空 链 \begin{aligned} L \rightarrow&S\\ \{ &L.CHAIN := S.CHAIN\}\\ L^S \rightarrow&L;\\ \{ &BACKPATCH(L.CHAIN,NXQ) \} \\ L \rightarrow&L^S\ S^{(1)}\\ \{ &L.CHAIN := S^{(1)}.CHAIN\}\\ S\rightarrow&begin\ L\ end\\ \{ &S.CHAIN := L.CHAIN\}\\ S \rightarrow&A\\ \{ &S.CHAIN := 0\} 空链\end{aligned} L→{LS→{L→{S→{S→{​SL.CHAIN:=S.CHAIN}L;BACKPATCH(L.CHAIN,NXQ)}LS S(1)L.CHAIN:=S(1).CHAIN}begin L endS.CHAIN:=L.CHAIN}AS.CHAIN:=0}空链​


语法制导翻译和中间代码生成

语法制导翻译和中间代码生成

上图中省略了 W H I L E WHILE WHILE 归约为 W W W,保存 W . Q U A D W.QUAD W.QUAD 的过程


语法制导翻译和中间代码生成

**分叉语句的翻译

  • 形式:
// E 是一个整型表达式
// Ci 为常数
// Si 是语句
case E of 
	C1: 
		S1
	C2: 
		S2
	…
	Cn-1: 
		Sn-1 
otherwise: 
	Sn 
end
  • 分叉只有 10 个左右时,翻译成条件转移语句
T := E;

L1: 
IF T≠C1 GOTO L2
S1;
GOTO NEXT 

L2: 
IF T≠C2 GOTO L3
S2;
GOTO NEXT;

L3: ......

Ln-1: 
IF T≠Cn-1 
GOTO Ln
Sn-1;
GOTO NEXT; 

Ln: Sn;
NEXT:
  • 开关表方法
    • 编译程序构造下面的开关表
    • 产生将 E E E 值送到该表末项的指令组,以及一个对 E E E 值查找开关表的程序
    • 运行时,循环程序对 E E E 值查开关表,当 E E E 与某个 C i C_i Ci​ 匹配就执行 S i S_i Si​
      语法制导翻译和中间代码生成
    • 如果 c a s e case case 的分叉情况比较多,最好建立 Hash 表。求出 H ( C i ) H(C_i) H(Ci​), 在其中填入 C i C_i Ci​ 和 S i S_i Si​;有的表项为空。运行时,求 H ( E ) H(E) H(E) 值,找对应表项;如空白,则执行 S n S_n Sn​
      语法制导翻译和中间代码生成
  • 一种便于语法分析制导实现的中间码形式
T := E 的中间码
Goto TEST

L1: 关于S1的中间码
Goto NEXT

L2: 关于S2的中间码
Goto NEXT

…

Ln-1: 关于Sn-1的中间码
Goto NEXT

Ln:  关于Sn的中间码
Goto NEXT

TEST:  
IF T=C1 GOTO L1;
IF T=C2 TOTO L2;…
IF T=Cn-1 GOTO Ln-1
Goto Ln
NEXT:

过程调用的翻译

  • 过程调用 (转子) 的难点:转移目标、返回地址、参数传递
    语法制导翻译和中间代码生成
  • 文法 G G G:
    ( 1 ) S → C A L L   i ( a r g l i s t ) ( 2 ) a r g l i s t → a r g l i s t , E ( 3 ) a r g l i s t → E \begin{aligned}(1) S &\rightarrow CALL\ i(arglist)\\ (2) arglist &\rightarrow arglist,E\\ (3) arglist &\rightarrow E\end{aligned} (1)S(2)arglist(3)arglist​→CALL i(arglist)→arglist,E→E​

语义动作

常用方案

  • 主程序: 实 参 → 约 定 单 元 实参\rightarrow约定单元 实参→约定单元
  • 子程序: 约 定 单 元 → 形 参 约定单元\rightarrow形参 约定单元→形参
  • 指令携带参数地址,把实参地址统一放在转子指令前,如下所示
    如 C A L L   S ( A + B , Z ) CALL\ S(A+B,Z) CALL S(A+B,Z) 翻成
    K − 4 : { T : = A + B } K − 3 : P a r   T K − 2 : P a r   Z K − 1 : C a l l   S K : … \begin{aligned} &K-4: \{T := A+B\}\\ &K-3: Par\ T\\ &K-2: Par\ Z\\ &K-1: Call\ S\\ &K: …\end{aligned} ​K−4:{T:=A+B}K−3:Par TK−2:Par ZK−1:Call SK:…​
    • 困难:如何在处理参数串的过程之中记住每个实参的地址,以便最后将它们排列在转子指令的前面
    • 解决:第一个实参建立队列,后面的循序记录,保持队列头

S → C A L L   i ( a r g l i s t ) { F O R   队 列 a r g l i s t . Q U E U E 的 每 一 项 P   D O         G E N ( p a r , _ , _ , p ) ; G E N ( c a l l , _ , _ , E N T R Y ( i ) ) } a r g l i s t → a r g l i s t ( 1 ) , E { 把 E . P L A C E 排 在 a r g l i s t ( 1 ) . Q U E U E 的 末 端 ; a r g l i s t . Q U E U E : = a r g l i s t ( 1 ) . Q U E U E } a r g l i s t → E { 建 立 一 个 a r g l i s t . Q U E U E , 它 只 包 含 一 项 E . P L A C E } \begin{aligned}S \rightarrow&CALL\ i(arglist)\\ \{&FOR\ 队列arglist.QUEUE的每一项P\ DO\\ &\ \ \ \ \ \ \ GEN ( par,\_,\_,p);\\ &GEN (call,\_,\_,ENTRY(i))\}\\ arglist \rightarrow&arglist^{(1)},E\\ \{ &把E.PLACE排在arglist^{(1)}.QUEUE的末端;\\ &arglist.QUEUE := arglist^{(1)}.QUEUE\}\\ arglist \rightarrow&E\\ \{ &建立一个arglist.QUEUE,它只包含一项E.PLACE\}\end{aligned} S→{arglist→{arglist→{​CALL i(arglist)FOR 队列arglist.QUEUE的每一项P DO       GEN(par,_,_,p);GEN(call,_,_,ENTRY(i))}arglist(1),E把E.PLACE排在arglist(1).QUEUE的末端;arglist.QUEUE:=arglist(1).QUEUE}E建立一个arglist.QUEUE,它只包含一项E.PLACE}​



C A L L   S ( A + B , Z ) CALL\ S(A+B,Z) CALL S(A+B,Z)
( + , E N T R Y ( A ) , E N T R Y ( B ) , T ) ( p a r , _ , _ , T ) ( p a r , _ , _ , Z ) ( C a l l , _ , _ , E N T R Y ( S ) ) \begin{aligned} &(+,ENTRY(A),ENTRY(B),T)\\ &(par, \_, \_, T)\\ &(par, \_, \_, Z)\\ &(Call,\_, \_, ENTRY(S))\end{aligned} ​(+,ENTRY(A),ENTRY(B),T)(par,_,_,T)(par,_,_,Z)(Call,_,_,ENTRY(S))​

写法二义性

X : = A ( I , J ) X := A(I,J) X:=A(I,J)

  • 过程调用或者数组引用,两者难以区分。而语法制导翻译是按语法规则(产生式)进行的,上下文无法区分,造成了翻译的困难
  • 解决方案
    • 查符号表
    • 词法分析器在发送 A A A 之前先查表确定其特性
    • 规定数组用 [   ] [\ ] [ ],避免冲突

声明语句的翻译

  • 声明语句如
Integer L, M, N;
ARRAY A;
  • 语义动作是把 L , M , N , A L,M,N,A L,M,N,A 登记到符号表中,并在相应位置填入整型等性质

声明语句文法, G [ D ] G[D] G[D]
D → i n t e g e r   n a m e l i s t   ∣   r e a l   n a m e l i s t n a m e l i s t → n a m e l i s t , i   ∣   i \begin{aligned}D \rightarrow&integer\ namelist\ |\ real\ namelist\\ namelist \rightarrow&namelist,i\ |\ i\end{aligned} D→namelist→​integer namelist ∣ real namelistnamelist,i ∣ i​

  • 该文法存在的问题:该文法要求把完整的 n a m e l i s t namelist namelist 读完才能做语义动作(在符号表中登记性质)。这样,就必须用栈、队列来保存所有这些名字

改造文法, G [ D ] G[D] G[D]
D → D , i   ∣   i n t e g e r   i   ∣   r e a l   i D \rightarrow D,i\ |\ integer\ i\ |\ real\ i D→D,i ∣ integer i ∣ real i

D → i n t e g e r   i { F I L L ( E N T R Y ( i ) , i n t ) ; D . A T T : = i n t } D → r e a l   i { F I L L ( E N T R Y ( i ) , r e a l ) ; D . A T T : = r e a l } D → D ( 1 ) , i { F I L L ( E N T R Y ( i ) , D ( 1 ) . A T T ) ; D . A T T : = D ( 1 ) . A T T } \begin{aligned}D \rightarrow& integer\ i\\ \{&FILL(ENTRY(i),int);D.ATT := int\}\\ D \rightarrow&real\ i\\ \{&FILL(ENTRY(i),real);D.ATT := real\}\\ D \rightarrow&D^{(1)},i\\ \{&FILL(ENTRY(i),D^{(1)}.ATT);D.ATT := D^{(1)}.ATT \}\end{aligned} D→{D→{D→{​integer iFILL(ENTRY(i),int);D.ATT:=int}real iFILL(ENTRY(i),real);D.ATT:=real}D(1),iFILL(ENTRY(i),D(1).ATT);D.ATT:=D(1).ATT}​


数组说明

  • 数组声明语句如
    A r r a y   A [ l 1 : u 1 , l 2 : u 2 , … , l n : u n ] Array\ A[l_1:u_1,l_2:u_2,…,l_n:u_n] Array A[l1​:u1​,l2​:u2​,…,ln​:un​]
    • l i , u i l_i,u_i li​,ui​ 为第 i i i 维的上下界, n n n 为维数
  • 在处理数组时,通常会把数组的有关信息记录在内情向量中。对于静态数组,内情向量可以放在符号表中;对于动态可变数组,将在运行时建立相应的内情向量
    语法制导翻译和中间代码生成
    • d i = u i − l i + 1 d_i=u_i-l_i+1 di​=ui​−li​+1 为第 i i i 维的长度
    • t y p e type type 为数组元素的类型
    • a a a 为数组首元素的地址
    • C C C 为计算元素偏移地址时不变的部分,例如:
      语法制导翻译和中间代码生成

数组声明需要完成的工作:填向量申请空间计算下标地址

  • A A A 为确定数组
    • 每维的上下限 l , u l,u l,u 都是常数,长度 d d d, 体积 A A A 都可以计算。编译时可填所有元素
    • 由于下标变量度地址的计算涉及的 l , u l,u l,u 都是已知量,所以运行时可以不要信息向量,理论上只要保留工作单元和一些常数就可以了
  • A A A 为可变数组
    • l , u l,u l,u 是变量,体积要在运行时计算确定
    • 编译时分配向量区,是空架子
    • 编译时产生计算 l , u l,u l,u 的指令组,填入向量区中
    • 根据 l , u l,u l,u ,申请内存区的指令

可变数组分配子程序

  • 输入:维数 n n n,界限序列 l 1 , u 1 , l 2 , u 2 , … l n , u n l_1, u_1, l_2, u_2, …l_n, u_n l1​,u1​,l2​,u2​,…ln​,un​,类型 t y p e type type 以及内情向量表区地址
  • 功能:建立内情向量并分配数组空间
BEGIN
	i:=1; N:=1; C:=0; 
	WHILE i ≤ n  DO
	BEGIN
		di := ui –li +1; 
		N:=  N * di;
		C := C * di + li;
		把 li,ui 和 di 填入内情向量表区中;
		i := i + 1
	END;
申请 N 个单元的数组空间,令这片空间的首地址为 a;
把 n 和 C,以及 type 和 a 填入内情向量区中
END;
上一篇:android Http重定向


下一篇:iptables-基础规则