继续向语法树节点结构中追加成员。
#define memberAbstractSyntaxNode \ unsigned int line; \ struct AbstractSyntaxNode* nextNode; \ struct List* (*createInstruction)(void*); \ void (*delNode)(void*); \ void (*printree)(void*, int); /***********/ // AbstractSyntaxNode 增加成员 createInstruction // 产生该语法树节点对应的指令 List #define memberAbstractValueNode \ memberAbstractSyntaxNode \ int (*staticInt)(void*, int*); \ int (*staticReal)(void*, double*); \ AcceptType (*typeOf)(void*); \ struct List* (*addressOf)(void*); // AbstractValueNode 增加成员 addressOf // 产生这个值节点对应的加载地址的指令 List // 对于非法的左值, 如表达式, 这个函数返回 NULL
这篇文章将谈及我们需要实现的接口和它们的功能。
对于每类节点的指令生成模块,函数命名方式为
ins + 类名
如
struct List* insIntegerNode(void*);
就是为 IntegerNode 生成指令的函数,它将在 newIntegerNode 函数(希望大家还记得这些函数)中被赋值:
node->createInstruction = insIntegerNode;
其它类型与这个例子非常类似,就不一一赘述了。下面谈谈各类型指令生成是要干什么。
// 进入基本块和退出基本块 // 函数将连接块内所有语句的指令, 此外还将与符号表进行交互 struct List* insBasicBlockNode(void*); // 一元 / 二元运算节点 // 加载它们的子节点(运算数)的指令, 并进行对应的运算(例外参见后文) // 执行它们生成的指令, 结果是让运行栈栈顶存放一个值, 而不是一个地址 struct List* insBinaryOperationNode(void*); struct List* insUnaryOperationNode(void*); // 常数节点生成的指令将加载一个常数到运行栈栈顶 struct List* insIntegerNode(void*); struct List* insRealNode(void*); // 分支控制生成的指令将根据运行栈栈顶的整数值, 调节执行方式 struct List* insIfElseNode(void*); // 循环控制除了生成循环控制相关的指令 // 还会维护一个循环栈 // 而 BreakNode 将根据循环栈来确定该跳出哪个循环 // insBreakNode 包含语义容错 struct List* insWhileNode(void*); struct List* insBreakNode(void*); // IO 是一个统称 // 实际上, Read 与 Write 生成的指令很不一样 // Read 执行时, 要求运行栈栈顶是一个地址 // 然后根据类型读入数据并送往该地址 // 而 Write 要求栈顶是值, 并写出该值 // 指令执行之后, 将弹走栈顶 struct List* insIONode(void*); // 一个算术节点会干什么? // 它并不是仅仅调用子节点的指令生成就完了 // 注意到, 当一个运算结束之后, 运行栈栈顶总会有一个值 // 因此为了维护栈指针, 算术节点会根据子节点的数据类型调整栈指针 struct List* insArithmaticNode(void*); // 声明节点主要工作是与符号表打交道 // 以及对变量进行初始化 // 包含语义容错 struct List* insDeclarationNode(void*); // 将变量的运行时值取到栈顶 // 包含语义容错 struct List* insVariableNode(void*);
有一点很有趣的是,无论是x86指令集,还是Jerry指令集,都不包含与/或/非(逻辑运算,不是位运算)这些运算,也就是说我们必须完成条件短路,所以虽然 BinaryOperationNode 就只有一个指令生成入口,但是这个入口会根据运算符的不同而进入三个不同的模块中
// 运算符是 = 那么进行赋值指令生成 static struct List* insAssignment(void*); // 运算符是 && 或 || 进行逻辑短路 static struct List* insLogicShortcut(void*); // 此外生成一般运算指令 static struct List* insNormalBinaryOp(void*);
为了方便判断,在 const.h 中 AcceptType 旁边添加这些宏
#define isArithOperator(x) (PLUS <= (x) && (x) < ASSIGN) #define isLogicOperator(x) (AND <= (x) && (x) <= NOT)
而对于取地址函数,就目前的情况来看只有两种——变量地址,和无法取到地址。这些接口如下
// 取变量的地址, 指令运行时执行结束将会在栈顶引入该变量的地址 struct List* addrOfVar(void*); // 其它 struct List* cantRetrieveAddr(void*);
记得将它们在 newVariableNode 等函数中绑定到对应的 addressOf 域中。