继续向语法树节点结构中追加成员。

 


#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