[符号表]静态地址管理符号表接口及使用原则

    进入语义分析的首要任务是组织和管理变量符号。这一点有多种选择,一种是类C/C++语言那样的静态处理,即将变量映射为地址,运行时内存中根据地址取得变量值,这样运行效率无论是从内存使用量和访问速度上都会较高;而另一种就是可反射的符号表,即在运行时根据变量名在符号表中取得变量值,这样做的典型是Python语言,虽然这样效率较低,不过好处是方便ORM等反射机制的实现。不过既然现在Jerry并没有意向弄得那么复杂,因此就采用前一种。

    首先,对于这样的符号表,基本的操作包括:

    - 声明变量,将变量存入符号表

    - 取得变量类型

    - 取得变量首地址

    - 对于数组,取得变量某一维的大小

第一个是必须的毫无疑问。不过有一个问题,那就是调用该函数,传入的参数是什么。是一个DeclarationNode吗?当然这是可以的,但是这样就违反了数据封装的原则,即在设计该函数时必须对DeclarationNode的内部结构有了解,这加大了难度。所以可以考虑其参数全部为基本数据类型。包括其它函数也是这样,在取得变量信息时全部使用基本数据类型,而不用VariableNode之类的东西。

void regVar(char* ident, AcceptType type, int nrDim, int* dims);
AcceptType getVarType(char* ident);
int getVarAddr(char* ident);
int getVarNrDim(char* ident);
int getVarDimSize(char* ident, int index);

其中函数的作用分别是

    regVar        在符号表中注册一个变量,给出标识符、类型及维度信息

    getVarType    根据标识符获取变量类型

    getVarAddr    根据标识符获取变量首地址

    getVarNrDim   根据标识符获取变量的维度数

    getVarDimSize 根据标识符和某一维的大小

当然,第一个函数就没什么好说的了。第二个函数获取类型是为了做静态上下文类型判断与转型,如 i 是一个整型变量而 j 是一个实型变量,如有一条语句为

    i = j;

那么在运行时会将先将 j 的值放在栈顶,然后转换成整型,再赋给 i 所在的地址。getVarAddr则是用来获取变量在内存中的首地址,对于数组变量也是这样,数组变址则要靠最后一个函数。getVarNrDim函数用以获取变量的维度,在访问变量时,首先要对其维度数量进行匹配(因为Jerry没有考虑支持数组,所以引用时的变量维度数量必须跟声明时一样)。现在来看最后一个函数。当对一个数组变量进行合法引用时,比如

    i[1][2][x]

这时为了获取这个变量的地址,该怎么办呢?注意到最后一维的下标是个变量,这也就意味着无法通过静态计算来获得,必须动态绑定。过程是这样的,首先查询 i 在声明时各维度大小,假设是d0d1d2i 的首地址是a0,那么首先计算 i[1][2] 的地址:

    a0d1 * d2 * 1 + d2 * 2

在运行时再将这个地址与 x 的值相加,就得到了该变量的值,至于取得d0d1d2这些信息,就得靠函数getVarDimSize了。(注意:以上运算并不正确,因为地址偏移并未考虑到每个该类型变量占用的字节数,必须在运算时乘算该大小因子。)为了避免每次在获取数组高维度的偏移时都要重复计算(如刚才例子中最高维度的偏移是d1 * d2),可以在调用regVar时,让符号表中存放的数组维度信息就是偏移,而不是每个维度的大小。

 

当然符号表管理并不是这样就好了,以上这些接口是相当理想化的接口,它无法处理两件事:

    - 出错

    - 内层变量覆盖外层变量

为了对付出错,那么就得让函数有所“表示”,最基本的方法就是返回错误值,那么原来的返回值呢?答案是,将其地址作为参数传入,这是一种基本的策略,C语言库函数中的fgets也是这么干的,我们可以效仿之。至于花括号内外层这档子事儿,就必须采用使用多个符号表,并附加一个符号表栈来实现。一开始该栈里面有一个符号表,当到达基本块开头(也就是遇到正花括号)时,压入一个新的符号表进栈,直到基本块结束才将其弹出栈,当要获取变量信息时,依次从栈顶向下逐个对符号表进行查询,直到找到该变量,或者到达栈底的楼下(这时报错)。不过关于符号表栈这东西,并不由符号表本身来考虑,应该放在语义分析的控制部分或者中间插入一个变量管理层进行。对于符号表,现在比较合理的接口形式是(struct SymbolTable表示符号表结构体)

/* 符号表操作过程中可能的错误 */
/* const.h */
typedef enum {
    SymTab_OK = 0, // 无错误
    SymTab_MultiDef, // 重复定义(在同一基本块内, 内外层则会覆盖)
    SymTab_NotDef, // 找不到定义
    SymTab_SizeExceeded, // 大小超限, 由于声明的变量太多了或数组太大了
    SymTab_ArrDimOutOfRange // 数组维度访问越界, 请求维度大于维度数量
} SymbolTableError;

/* 新建一个符号表 */
struct SymbolTable* newSymbolTable(int base);
/* 让符号表退休 */
void finalizeSymbolTable(struct SymbolTable* table);

/* 参数名为 ret 的都是返回值,现在它们由地址传入,函数内部将返回值写入地址 */
SymbolTableError regVar(struct SymbolTable* table, char* ident,
                        AcceptType type, int nrDim, int* dims);
SymbolTableError getVarType(struct SymbolTable* table, char* ident,
                            AcceptType* ret);
SymbolTableError getVarAddr(struct SymbolTable* table, char* ident,
                            int* ret);
SymbolTableError getVarNrDim(struct SymbolTable* table, char* ident,
                             int* ret);
SymbolTableError getVarDimSize(struct SymbolTable* table, char* ident,
                               int* ret, int index);

不需要考虑符号表的继承(这难道还面向对象?),所以这些函数就这么静态弄,不用变成成员函数来搞。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值