【发布时间】:2014-02-04 16:35:23
【问题描述】:
我正在用 C 实现一个简单玩具语言的编译器。我有一个工作扫描器和解析器,并且对 AST 的概念函数/构造有合理的背景。我的问题与在 C 中表示 AST 的具体方式有关。我在网上的不同文本/资源中经常遇到三种样式:
每种类型的节点一个结构。
这有一个基节点“类”(结构),它是所有子结构中的第一个字段。基节点包含一个存储节点类型(常量、二元运算符、赋值等)的枚举。使用一组宏访问结构的成员,每个结构一组。它看起来像这样:
struct ast_node_base {
enum {CONSTANT, ADD, SUB, ASSIGNMENT} class;
};
struct ast_node_constant {
struct ast_node_base *base;
int value;
};
struct ast_node_add {
struct ast_node_base *base;
struct ast_node_base *left;
struct ast_node_base *right;
};
struct ast_node_assign {
struct ast_node_base *base;
struct ast_node_base *left;
struct ast_node_base *right;
};
#define CLASS(node) ((ast_node_base*)node)->class;
#define ADD_LEFT(node) ((ast_node_add*)node)->left;
#define ADD_RIGHT(node) ((ast_node_add*)node)->right;
#define ASSIGN_LEFT(node) ((ast_node_assign*)node)->left;
#define ASSIGN_RIGHT(node) ((ast_node_assign*)node)->right;
每个节点布局一个结构。
这似乎与上面的布局基本相同,除了没有 ast_node_add 和 ast_node_assign 它将有一个 ast_node_binary 来表示两者,因为这两个结构的布局是相同的,它们仅在 base 的内容上有所不同->类。这样做的好处似乎是一组更统一的宏(LEFT(node) 用于所有节点,每个节点都有左右而不是一对宏),但缺点似乎是 C 类型检查不会那么有用(例如,没有办法检测到应该只有 ast_node_add 的 ast_node_assign)。
一个结构体,一个联合体来保存不同类型的节点数据。
可以在here 找到比我能给出的更好的解释。使用上一个示例中的类型,它看起来像:
struct ast_node {
enum { CONSTANT, ADD, SUB, ASSIGNMENT } class;
union { int value;
struct { struct ast_node* left;
struct ast_node* right; } op;
};
我倾向于最喜欢第三个选项,因为它使递归遍历更容易(因为避免了很多指针转换以支持联合),但它也没有利用 C 类型检查。第一个选项似乎是最危险的,因为它依赖于指向被强制转换的结构的指针来访问任何节点的成员(即使是同一节点的不同成员需要不同的情况来访问(base vs. left)),但这些强制转换是类型检查所以可能没有实际意义。对我来说,第二种选择似乎是两全其美的选择,尽管我可能遗漏了一些东西。
这三个方案中哪一个是最好的,为什么?我还没有遇到更好的第四个选项吗?我假设它们都不是“一刀切”的解决方案,所以如果重要的话,我正在实现的语言是静态类型的命令式语言,几乎是 C 的一小部分。
关于第三个(联合)布局的具体问题。 如果我只使用 value 字段,在 value 后面是否会有空格以适应写入操作的可能性?
【问题讨论】:
标签: c compiler-construction struct tree abstract-syntax-tree