进入语义分析的首要任务是组织和管理变量符号。这一点有多种选择,一种是类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 在声明时各维度大小,假设是d0,d1,d2, i 的首地址是a0,那么首先计算 i[1][2] 的地址:
a0 + d1 * d2 * 1 + d2 * 2
在运行时再将这个地址与 x 的值相加,就得到了该变量的值,至于取得d0,d1,d2这些信息,就得靠函数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);
不需要考虑符号表的继承(这难道还面向对象?),所以这些函数就这么静态弄,不用变成成员函数来搞。
分享到:
相关推荐
3. **符号表的接口函数**:为了在编译器的不同组件之间共享和操作符号表,通常会提供一组接口函数,例如添加新标识符、查找标识符、更新标识符属性等。 4. **类型的语义分析**:此阶段检查每个表达式的类型是否正确...
10. 存储器扩展:位扩展和容量扩展分别采用芯片组法和地址译码法,连线原则是保证数据和地址的正确连接。 11. 指令格式:基本的指令格式由操作码和操作数组成。 12. 寻址方式:8086/8088CPU支持10种寻址方式,包括...
路由来源代码符号表则解释了这些路由信息的获取途径,如直连路由、静态路由和动态路由。 管理距离和代价是路由决策的重要依据,如图5-1-4所示。管理距离是衡量路由信息可信度的一个指标,数值越小,表示路由信息越...
9. **代码结构**:遵循SOLID原则,包括单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。使用设计模式如工厂模式、观察者模式、装饰器模式等提高代码灵活性。 10. **单元测试**:编写单元测试来...
【微机原理与接口技术】是一门深入理解计算机硬件与软件交互的学科,涉及的知识点广泛且基础。从试卷内容来看,主要考察以下几个核心概念: 1. **微型计算机系统组成**:微型计算机系统通常由中央处理器(CPU)、...
UML是一种可视化建模语言,它提供了一套图形符号来描述系统的静态结构和动态行为。在设计学生信息管理系统时,UML可以帮助我们: 2.1 统一建模语言UML UML包括了类图、对象图、用例图、序列图、协作图、状态图、...
- 汇编语言:使用助记符和地址符号编写程序的语言,比机器语言更易读。 - 汇编语言程序:使用汇编语言编写的源代码。 - 汇编程序:将汇编语言程序翻译成机器语言的工具,是编译器的一种。 8. 处理器结构相关知识...
微型计算机原理及应用是计算机科学中的基础领域,涵盖了微处理器设计、存储系统、输入输出接口、总线系统以及数据处理等方面的知识。以下是一些相关的知识点: 1. 微型计算机:这种计算机以微处理器为核心,结合了...
- BX、BP、SI、DI:这些寄存器可以作为地址寄存器使用,用来访问存储单元。 3. **标志位的名称及意义**: - CF:进位标志位,当算术运算中有进位或借位时置1。 - AF:辅助进位标志位,主要用于BCD码运算中的调整...
《基于UML的酒店客房管理系统设计》是一份深入探讨计算机软件综合设计的文档,它主要关注如何运用统一建模语言(UML)来构建一个高效、实用的酒店客房管理系统。UML作为软件工程中的重要工具,是系统分析与设计的...
在这个"Java_API获取人类最鼓舞人心的表情集合.zip"中,我们可以推测这可能是一个库或者框架,用于在应用程序中展示或处理鼓舞人心的表情符号,可能是为了提升用户的情感体验或增强社交互动。 首先,我们需要解压这...
微机原理是计算机科学的基础,涉及微处理器结构、内存管理、数据...以上知识点覆盖了微机原理的多个方面,包括微处理器结构、内存管理、数据表示、地址计算、中断系统、I/O接口等,是准备微机原理考试的重要参考资料。
该标准分为十一个部分,分别是:目录、前言、介绍、范围、规范性引用、术语及定义、符号及缩略词、系统框架、车辆自动运行功能的要求、管理功能要求、停车设施内的环境要求、整体系统运行要求、自动车辆运行测试场景...
12. 堆栈操作:8086系统中,堆栈遵循后进先出(LIFO)原则,压栈时地址减小,弹栈时地址增大。 13. 8086与8088的区别:8086有16位数据总线,而8088是8位数据总线,但都具有20位地址总线。 14. 总线控制器8288:...
地址绑定是指将程序中的符号引用与其实际内存地址相匹配的过程。早期的编程环境中,这一过程主要在程序执行时由加载器完成。随着技术的发展,地址绑定可以在链接阶段进行,这样可以提高程序的执行效率,减少运行时的...
- 构件(静态部分,描述概念或物理元素):如类、接口、组件、节点等,代表了系统中的静态实体。 - 行为(动态部分,描述跨越空间和时间的行为):如状态机、活动、交互等,描述了系统中对象的行为特征。 - 分组...
在使用匿名内部类时,要记住以下几个原则:匿名内部类不能有构造方法、匿名内部类不能是public、protected、private、static、只能创建匿名内部类的一个实例、匿名内部类不能定义任何静态成员、静态方法、一个匿名...
* 骆驼法则:Java 中,除了包名、静态常量等特殊情况,大部分情况下标识符使用骆驼法则,即单词之间不使用特殊符号分割,而是通过首字母大写来分割。 例如:supplierName, addNewContract,而不是 supplier_name, ...