<编译器> 7. 中间代码 | 2.抽象语句 --> 树中间语言

1.    表达式  A_exp -> T_exp , T_stm


 

struct Tr_exp_{
    // Tr_ex 表达式, Tr_nx 无结果语句, Tr_cx 每件语句
    enum {Tr_ex, Tr_nx, Tr_cx}kind;
    union {T_exp exp, T_stm nx, struct Cx cx;}u;
};

struct Cx {patchList trues; patchList falses; T_stm stm;}

 

复制代码
// Tr_exp 构造函数
static Tr_exp Tr_Ex(T_exp ex); // T_exp -> Tr_exp
static Tr_exp Tr_Nx(T_stm nx); // T_stm -> Tr_exp
static Tr_exp Tr_Cx(patchList trues, patchList falses, T_stm stm); // T_stm + 回填表 -> Tr_exp

// 相当于以上构造函数的逆转
static T_exp unEx(Tr_exp e); // Tr_exp -> T_exp
static T_stm unNx(Tr_exp e); // Tr_exp -> T_stm
static struct Cx unCxc(Tr_exp e); // Tr_exp -> struct Cx
复制代码

 

解析表达式   a>b | c<d :
复制代码
Temp_label z = Temp_newlabel();
// 这里返回 T_stm 类型,因为是 无结果语句
T_stm s1 = T_Seq(
    // 如果 a>b,语句为 true, 不用再判断后面的 c<d,跳转到 NULL 稍后回填 true patch list
    // 如果 a<b,语句为 false, 则需要判断后面的 c<d, 跳转到标号 z
    T_Cjump(T_gt, a, b, NULL, z), 
    T_Seq(
        T_Label(z), // 标号z ,代表语句 "c<d"
        T_Cjump(T_lt, c, d, NULL, NULL)  // 判断 c<d
    )
);
复制代码

 

T_Cjump(T_gt, a, b, NULL, z), 中的 NULL 对应真值 稍后回填: true patch list
T_Cjump(T_lt, c, d, NULL, NULL) , 中的 两个NULL 对应真值和假值 稍后回填: true patch list, false patch lsit 
 
1.2    真/假值标号回填表  true patch list / false patch list
 
以下语句为例 
 
if c>d
then ts...
条件类表达式 c > d , 真分支 ts... ,  无假分支
 
(1)    解析定义阶段 
if c>d
   这时还没解析到到后面对应的真/假分支语句,先都填入 NULL
        T_stm stm1 = T_Cjump(T_gt, c, d, NULL, NULL);
        把两个 NULL 对应的语句和位置记录在 回填表 patchLis 中:
 
 
真值回填表 假值回填表
stm1.u.CJUMP.true stm1.u.CJUMP.false
 (2)    解析真值语句
 
then ts ...
        这时生成真值对应的标号 Temp_label t,更新 stm1 对象 为
        stm1 = T_Cjump(T_gt, c, d,t, NULL);
 (3)    解析假值语句
   没有假值语句,语句不变
 (4)    结束
 
 1.3    表达式的转换 达式1 := 表达式2
 以下将 条件表达式 struct Cx (a>b | c<d) 转换为 返回值表达式 T_exp flag
 
flag := (a>b | c<d) // struct Cx (a>b | c<d) ---> T_exp flag;

T_exp unEx(Tr_exp e) : 将条件表达式 Cx,  无值表达式 Nx, 值表达式 Ex 转化为 "值表达式"  T_exp

 
复制代码
// 条件结构体 struct Cx {patchList trues; patchList falses; T_stm stm};
// T_Eseq(T_stm stm, T_exp exp) 执行 stm, 返回 exp
// T_exp T_Temp(Temp_temp temp) 生成一个临时变量
static T_exp unEx(Tr_exp e) {
    switch (e->kind) {
        case Tr_ex: // 有返回值表达式
            return e->u.ex;

        case Tr_nx:
            // 1. 先计算无返回值表达式 e->u.nx
            // 2. 返回值固定设为 T_Const(0))
            return T_Eseq(e->u.nx, T_Const(0));

        case Tr_cx:
            Temp_temp r = Temp_newtemp();
            T_exp er = T_Temp(r) // 返回值

            // t:真值回填标号 f:假值回填标号
            Temp_label t = Temp_newlabel(), f = Temp_newlabel();
            // 将真/假值回填表中的标号全部换成 t, f
            doPatch(e->u.cx.trues, t);
            doPatch(e->u.cx.falses, f);

            return T_Eseq(T_Move(er, T_Const(1)), // er 先默认赋值 1
                          T_Eseq(e->u.cx.stm, // 执行条件判断语句,如果为true 就会跳转到 t, 否则跳转 f
                                 T_Eseq(T_Label(f), // 跳转到 f
                                        T_Eseq(T_Move(er, T_Const(0)), // er 赋值为 0
                                                      T_Eseq(T_Label(t),  // 跳转到 t
                                                             er // 最后返回 er (为1或0)
                                                      )))));
    }
    assert(0);
}
复制代码

 T_stm unNx(Tr_exp exp):转换为无返回值语句

 
复制代码
// T_stm T_Seq(T_stm left, T_stm right);
static T_stm unNx(Tr_exp exp){
    switch (exp->kind) {
        case Tr_ex:
            return T_Exp(exp->u.ex);

        case Tr_nx:
            return exp->u.nx;

        case Tr_cx:
            Temp_temp r = Temp_newtemp();
            T_exp er = T_Temp(r) // 返回值

            // t:真值回填标号 f:假值回填标号
            Temp_label t = Temp_newlabel(), f = Temp_newlabel();
            // 将真/假值回填表中的标号全部换成 t, f
            doPatch(e->u.cx.trues, t);
            doPatch(e->u.cx.falses, f);

            return T_Seq(exp->u.cx.stm, T_Seq(T_Label(t), T_Label(f)));
    }
}
复制代码

 

 T_exp unCx(Tr_exp e) :  exp 转换为条件语句 结构体
 

复制代码
// struct Cx {patchList trues; patchList falses; T_stm stm}
// T_stm T_Cjump(T_relOp op, T_exp left, T_exp right, Temp_label true, Temp_label false) 为真时跳转 true,否则 false
static struct Cx unCx(Tr_exp exp){
    struct Cx cx = (Cx)checked_malloc(sizeof (struct Cx));
    Temp_label t = Temp_newlabel(), f = Temp_newlabel();

    struct Cx cx;
    switch (exp->kind) {
        case Tr_ex:
            // cx.stm = T_Exp(exp.u.ex);
            cx.stm = T_Cjump(T_ne, exp->u.ex, T_Const(0),NULL, NULL);
            cx.trues = PatchList(&cx.stm->u.CJUMP.true, NULL); // 要跳转到的 true 分支
            cx.falses = PatchList(cx.stm->u.CJUMP.false, NULL); // 要跳转到的 false 分支
            return cx;

        case Tr_nx: // 不会走到这一步 ?
            cx.stm = exp->u.nx;
            cx.trues = NULL;
            cx.falses = NULL;
            return cx;

        case Tr_cx:
            return exp->u.cx;
    }
}
复制代码

 

 

2. 简单变量


 

复制代码
struct expty {
    Tr_exp exp; // 存放中间代码
    Ty_ty ty;
}

// 构造函数
struct expty expTy(Tr_exp e, Ty_ty t) {
    struct expty et;
    et.exp = e;
    et.ty  = t;
    return et;
}
复制代码
MEM (  BINOP  ( PLUS,  TEMP fp,  CONST k )

当调用父级函数中定义的变量时:   静态链
 
 
 

3. 静态链


 
编译原理中的静态链指的是在编译时就已经确定的程序执行路径,编译器会根据预先定义好的静态链来生成最终代码。
这种方式的优势在于编译后生成的代码可以直接执行,不需要再进行动态判断,因此执行效率更高。
访问外层定义的变量 x : 
// k1,..., kn : 变量 x 在每一导的栈帧中的位移
MEM(+(CONST kn, MEM(+(CONST k_n-1, ...
                     MEM(+(CONST k1, TEMP FP))      ))))
 

4. 数组 : 记录赋值 & 指针赋值


与 C 语言不同,Tiger 模糊了 对象 和 指针的区别
在C 语言中,如下赋值会报错,因为 将指针 b 赋值给了 数组 a
int a[12], b[12];
a = b;
但是 tiger 语言中,"a := b"   编译器自动进行了 "转换", 并转为 "指针赋值"
tiger 的 记录也是指针, 记录赋值  =  指针赋值
let 
	type intArray := array of int
    var a := intArray[12] of 0
    var b := intArray[12] of 7
in
	a := b
end
 
 

5. 左值  l-values , 结构化左值  structured l-values 



标量 scalar:整数指针 , 只有一个成员, 占用一个字存储,可以放在寄存器中。
Tiger 中所有的变量和左值都是标量, 数组 及 记录变量(record)也是指针。
结构化左值 :   structured l-values, 非标量的左值,C 中就是结构体, Pascal 中就是 数组 和 记录(record )
 
 

6. 下标,域选择    a[2],  a.val, ...


// s: 字长
// l: 下标下界, 一般为0
// a: 数组基地址
a[i] :  ( i - l ) x s + a 
左值与 MEM 结点: 
左值是地址
左传转换为右值 (): 从该地址中读数
左值赋值 (): 对地址存数
一开始将 MEM() 看成左值
MEM 即表示存入,也可以表示取出
 
 

7. 字符串


 
Pascal 语言的字符串:
长度固定的数组,文字常数的尾部填充空格
 
C 语言的字符串:
以字符数组的形式存储的,并以 ASCII值为0 的  '\0' 作为结尾。
 '\0' 称为 空字符(null character)或 终止符(terminator)
 
Tiger 语言的字符串:
任意8位码。  (2^8 = 256)
字符串指针指向:  |  长度数字 |  字符串本体   |    =>   汇编
 
 

8. 数组,记录  array & record


 
tiger 语言中,  record 的生存器为什么可以长于 创建他的过程?
在Tiger语言中,记录(record)的生存器(lifetime)可以长于创建它的过程,这通常与内存管理和作用域规则有关。
Tiger是一种教学用的编程语言,旨在帮助学生理解编译原理和内存管理的基本概念。
 
堆分配:在Tiger语言中,记录可能是在堆上分配的,而不是在栈上。栈上的变量通常在其作用域退出时自动销毁,而堆上的变量则需要在某个时候显式地释放。因此,即使创建记录的过程已经返回,只要还有对记录的引用存在,记录就会继续存在于堆上。
引用计数或垃圾收集:Tiger语言可能使用引用计数或垃圾收集机制来管理堆上的内存。这意味着记录的生存器不仅取决于创建它的过程,还取决于其他对记录的引用。只要还有对记录的引用存在,记录就不会被释放。
返回值和参数:记录可能作为函数或过程的返回值,或者作为参数传递给其他函数或过程。在这些情况下,记录的生存器会延伸到接收它的函数或过程的作用域。
全局变量或静态变量:记录也可能被存储为全局变量或静态变量,这意味着它们在整个程序执行期间都存在,而不是仅限于创建它们的过程。
因此,在Tiger语言中,记录的生存器不仅受创建它的过程控制,还受其他多种因素影响,包括内存分配策略、引用计数或垃圾收集机制、返回值和参数传递,以及变量的作用域和存储类别等。
 
堆分配 or 栈分配 ?
堆分配和栈分配是计算机内存管理的两种主要方式,它们各自具有不同的原理和特点。栈时报时销,不再使用自动销毁,堆则创建后需要垃圾回收,否则会导致性能问题。
 
 
记录的创建与初始化:
 
 
数组的创建与初始化:
 
 

9. while & for


while:
 
test:
		if not(conditions) goto done
        body  
        goto test
done:
 
break 语句:
1. 没有嵌套在其他 while 语句中:直接换成  JUMP done
2. 嵌套在其他 while 语句中: 
		transExp(body)中 每个 while 语句生成自己的 break{done 标号}
 
for:
重写为 while 语句
 
for i:lo to hi
do body
转为:
let var i := lo
	var limit := hi
in while i <= limit
	do	(body; i := i+1)
end
 

10. 函数调用: 静态链


 f(a1,...,an)
转化为: 
CALL ( NAME lf, [sl, e1, ..., en] )
 
 
 
 
posted @   Leon大帝  阅读(44)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示