用 C 表示抽象语法树

Posted

技术标签:

【中文标题】用 C 表示抽象语法树【英文标题】:Representing an Abstract Syntax Tree in 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 后面是否会有空格以适应写入操作的可能性?

【问题讨论】:

【参考方案1】:

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

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

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

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

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

【讨论】:

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

以上是关于用 C 表示抽象语法树的主要内容,如果未能解决你的问题,请参考以下文章

java AST 抽象语法树

java AST 抽象语法树

编译原理-第二章 一个简单的语法指导编译器-2.8 生成中间代码

golang编译原理

C++ 中的抽象语法树表示

Static Program Analysis - Chapter 2 代码的表征之抽象语法树