📄 vc.h
字号:
/******************************************************************************
*******************************************************************************
vc.h
头文件
本头文件定义了VSL编译器所使用的重要数据结构和常数
文件修改时间
============
2005年5月6日: Turbo C 版 (C) hfwang
*******************************************************************************
******************************************************************************/
/* 定义常数 */
#define TRUE 1
#define FALSE 0 /* 布尔常数 */
#define EOS 0 /* 字符串结束标志 */
#define HASHSIZE 997 /* 符号(HASH)表大小 */
#define R_UNDEF -1 /* 无效寄存器标志 */
/* 定义VSL的数据类型, 注意: 由于函数在VSL中可以先使用后定义, 而VSL的语法分析将
函数当变量统一处理. 从而在词法分析时可能出现不知类型的变量进符号表. 因此, 在
处理这样的变量时使用T_UNDEF. */
#define T_UNDEF 0 /* 未知类型 */
#define T_VAR 1 /* 局部变量 */
#define T_FUNC 2 /* 函数 */
#define T_TEXT 3 /* 字符串常量 */
#define T_INT 4 /* 整数常量 */
#define T_LABEL 5 /* 三地址码标号 */
/* 定义三地址指令编码, 共有12个运算符, 为了方便程序处理,增加一个未定义运算
TAC_UNDEF. */
#define TAC_UNDEF 0 /* 未定义运算 */
#define TAC_ADD 1 /* a := b + c */
#define TAC_SUB 2 /* a := b - c */
#define TAC_MUL 3 /* a := b * c */
#define TAC_DIV 4 /* a := b / c */
#define TAC_NEG 5 /* a := -b */
#define TAC_COPY 6 /* a := b */
#define TAC_GOTO 7 /* goto a */
#define TAC_IFZ 8 /* ifz b goto a */
#define TAC_IFNZ 9 /* ifnz b goto a */
#define TAC_ARG 10 /* arg a */
#define TAC_CALL 11 /* a := call b */
#define TAC_RETURN 12 /* return a */
/* 此外, 为了方便代码生成时对内存空间、代码标号和函数的处理, 我们另外增加一些
“伪指令”(这样的指令不出现在目标代码中). TACLABEL用于标记下一条语句,供
转向语句之用,TACLABEL的第一个参数是符号表中的一个类型为T_LABEL的变量,该
变量名是唯一的。TAC_VAR将定义一个局部变量,在代码生成时,该语句将对所定义的
变量分配一个在动态栈上的存储空间。TAC_BEGINFUNC和TAC_ENDFUNC标记函数的开始
和结束 */
#define TAC_LABEL 13 /* 产生一个标号 */
#define TAC_VAR 14 /* 产生一个变量 */
#define TAC_BEGINFUNC 15 /* 函数开始标记 */
#define TAC_ENDFUNC 16 /* 函数结束标记 */
/* 库函数将由外部文件提供,VSL提供两个库函数,即打印数值PRINTN和打印字符串
PRINTS,在此我们用一个数组记录外部函数的入口点,数组第0号元素记录PRINTN,第
1号元素记录PRINTS,数组的长度为2。
此外,假定库函数文件“lib”和汇编语言的头文件“header”和编译器在相同的工作
目录,当然,为了更方便,我们可用#define设定库文件目录 */
#define LIB_PRINTN 0
#define LIB_PRINTS 1
#define LIB_MAX 2
#define LIB_DIR ""
/* 设定库文件目录,当前值为空串,表示和编译器的目录相同,你也可将之设置为一个别
的目录,将lib和header文件拷贝到该目录下,如:#define LIB_DIR "D:\\lib\\",表
示库文件在D:\LIB目录下,注意:LIB之后一定不能少\\。*/
/* 由于很多结构有复杂的共用体和子域,为了方便书写和阅读,在此,对常用的一些域名
进行宏定义,如:符号表结构中有域val1,该域的数据类型是共用体,共用体的域有
字符指针类型的text和整数类型的val,如果:#define VAL1 val1.val和#define
TEXT1 val1.text,设sp是指向符号表的指针, 则:sp->val1.val可用sp->VAL1代替,
sp->val1,test可用sp->TEST1代替。 */
#define VAL1 val1.val /* 取val1的val */
#define TEXT1 val1.text /* 取val1的text */
#define VAL2 val2.val /* 取val2的val */
#define LABEL2 val2.label /* 取val1的label */
#define ADDR2 val2.val /* 取val1的val,但含义为地址偏移量 */
#define ETYPE res->type /* 取表达式结果res的type */
#define EVAL1 res->val1.val /* 取表达式结果res的值val */
#define VA a.var /* 取三地址码中第一运算量a的值val */
#define LA a.lab /* 取三地址码中第一运算量a的标号lab */
#define VB b.var /* 取三地址码中第二运算量b的值val */
#define LB b.lab /* 取三地址码中第二运算量b的标号lab */
#define VC c.var /* 取三地址码中第三运算量c的值val */
#define LC c.lab /* 取三地址码中第三运算量c的标号lab */
/* 以下是编译器的核心数据结构,符号表元素的数据类型SYMB是一个结构类型,有4个域:
next指向符号表元素的指针,type为该节点的类型,val1是一共用体,对常整数取
其值val,对变量取其形text, val2也是一样,在表示内存偏移量等时取整数val,在
表示三地址码标号时,取指向三地址码结构的指针label。指针next将不再使用的符号
表元素链结在一起,再次申请符号元素的内存单元时,直接从该链表中提取,从而避免
了对内存的频繁申请。
为了简化编译处理,整个VSL程序共享一个符号表,与此同时,我们将常数,标号、函
数和临时变量一并放入符号表统一处理,这样大大地简化了三地址码的数据结构 */
typedef struct symb /* 符号表节点的数据结构 */
{
struct symb *next ; /* 下一个节点 */
int type ; /* 符号的数据类型 */
union /* 变量的第一个值 */
{
int val ; /* 对整数常数取其值 */
char *text ; /* 对变量取其形,为字符指针 */
} val1 ;
union /* 变量的第一个值 */
{
int val ; /* 对变量取偏移量等 */
struct tac *label ; /* 对标号取三地址码指针 */
} val2 ;
} SYMB ;
/* 三地址码结构TAC是一个有4个域的双向链表,next指针指向下一个三地址码,prev指针
指向前一个三地址码,该指针供在代码生成处理时,返回第一个三地址码之用。域op
是操作码,域a是第一操作数,域b和c分别是第二和第三操作数,其数据类型均为共用体,
其成员或者是符号表指针var,或者是TAC指针。这4个域记录如下形式的三地址指令:
a := b op c */
typedef struct tac /* 三地址码双向链表的节点的数据类型 */
{
struct tac *next ; /* 向前指针 */
struct tac *prev ; /* 向后指针 */
int op ; /* 操作码 */
union /* 第一操作数 */
{
SYMB *var ; /* 变量名 */
struct tac *lab ; /* 三地址 */
} a ;
union /* 第二操作数 */
{
SYMB *var ;
struct tac *lab ;
} b ;
union /* 第三操作数 */
{
SYMB *var ;
struct tac *lab ;
} c ;
} TAC ;
/* 在语法处理时,对表达式的翻译,我们不仅需要将表达式翻译成三地址码,还需要记录
中间临时变量的内存地址,为此,增加一个结构ENODE, 记录临时变量,此外,为了处
理函数调用时参数列表,我们在ENODE中增加一个域next,使得中间临时变量形成一个单
向链表。 */
typedef struct enode /* 表达式链表 */
{
struct enode *next ; /* 链表指针 */
TAC *tac ; /* 表达式的三地址指令 */
SYMB *res ; /* 临时变量在符号表的指针 */
} ENODE ;
/* 以下是编译器所需的全局变量, 它们在main.c模块中定义,在其他的模块中引用。符号表
symbtab是一个哈西表,其元素的数据类型是指向符号单向链表的节点的指针。library
是一个数组,其元素是三地址码指针,该指针指向库函数的三地址码链表的头节点。
在翻译表达式时产生的中间临时变量,我们按正常的变量等同处理,对每个临时变量,
用“Tnnn”的形式表示,其中nnn是区别其他临时变量的唯一的整数,我们用next_tmp
表示该整数,每次产生临时变量时,该整数自动加1,同样,用“Lnnn”处理标号,并
用next_label作为标号的计数器 */
extern SYMB *symbtab[HASHSIZE] ; /* 符号表 */
extern TAC *library[LIB_MAX] ; /* 函数库数组 */
extern int next_tmp ; /* 临时变量计数器 */
extern int next_label ; /* 标号计数器 */
/* 以下是编译器的全局函数,其函数定义大部份在main.c模块,通过下面的引用说明这
些函数在其他模块中使用 */
extern SYMB *mkconst( int n ) ; /* 在main.c中定义 */
extern SYMB *mklabel( int l ) ;
extern SYMB *mktmp( void ) ;
extern TAC *mktac( int op,
SYMB *a,
SYMB *b,
SYMB *c ) ;
extern TAC *join_tac( TAC *c1,
TAC *c2 ) ;
extern void insert( SYMB *s ) ;
extern SYMB *lookup( char *s ) ;
extern SYMB *get_symb( void ) ;
extern void free_symb( SYMB *s ) ;
extern ENODE *get_enode( void ) ;
extern void free_enode( ENODE *expr ) ;
extern void *safe_malloc( int n ) ;
extern void error( char *str ) ;
extern void print_instr( TAC *i ) ;
extern void cg( TAC *tl ) ; /* 在cg.c中定义 */
/* extern void *malloc( int size ) ;*/ /* External routines */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -