【问题标题】:Representing an Abstract Syntax Tree in C用 C 表示抽象语法树
【发布时间】: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


    【解决方案1】:

    您可以使这些工作中的任何一个工作。

    我更喜欢联合布局,因为这样所有节点都有“相同”的布局。

    [您可能会发现有一个“子子列表”选项很有用,例如,任意大的动态子数组,而不是左倾或右倾列表。]

    您会发现这个问题并不是让您的编译器构建变得困难的问题。相反,它拥有符号表、执行各种分析、选择机器级 IR、构建代码生成器以及进行代码优化。然后你会遇到真正的用户,你会发现你真正做错了什么:-}

    我会选择一个并运行它,以便您有机会接近其他问题。

    【讨论】:

    • 谢谢!这正是我想听到的,很高兴知道我还没有偏离轨道。
    • @user1547129:如果可以的话,避免使用父指针会很烦人,但可能值得。我认为你在这个阶段并不真的需要它们,但是当你想在树周围移动并且必须取消链接并重新链接它们时,它们会让人头疼。
    • @Mehrdad:所有数据结构都很烦人,因为您必须确保它们(及其不变量)保持最新。您已经指出了在维护父指针和编码父列表缓存之间的权衡,以允许您在没有父链接时返回树。 (我个人更喜欢父链接;大多数涉及树的代码都在检查它,这应该是最容易编写的。[注意:我构建了进行大量树转换的工具]。)。 YMMV。
    • 工会是你选择的第二个吗?
    • 我不明白你的问题。我的回答似乎表明我更喜欢工会作为备选方案中的第一个。话虽如此,我的 DMS 系统以统一的方式处理 50 多种不同语言的 AST,根据使用频率有几个基线节点布局。但它不是一个非终端。我们使用终端节点(只有父链接、类型和值)、固定数量的子节点(具有 1 到 15 个子节点)和具有一个父节点和一个动态子节点数组的列表节点来捕获列表。当您进行长期投资时,可以拥有多种节点类型。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多