我目前正在探索设计一个可以在多个阶段转换AST的编译器。 我们的想法是,从解析树开始,每次传递都会转换树,直到生成的AST得到优化,并包含生成中间代码所需的树的每个节点中的所有必需信息(在本例中为LLVM IR)。 树上的传递可能会显着改变其结构,例如通过运算符优先级解析将运算符和操作数列表更改为有序操作的层次结构。 请注意,传递可能会使结构的某些部分完全不变。

所以,我的问题是我如何最好(阅读:最容易,尽可能少的重复)代表一个在C ++中有多个中间表示的AST? 我希望每个阶段的AST版本中的节点类型在编译时遵守它们的不兼容性。 我认为关键问题是如何在避免重复代码的同时代表结构中不改变通道的部分? 我想这是编译器作者过去多次解决的问题。

请注意,我目前在我的AST中使用Boost Variant而不是普通的运行时多态,并希望解决方案与它兼容。

===============>>#1 票数:4

AST节点本身不需要大量的复杂性。 我认为所有这些AST节点机器都是矫枉过正。

AST的问题不是节点类型安全; 它的树形安全。 AST代表(可能)某些语言L的某些有效实例。您理想的是要转换AST以生成其他有效的AST(语言L的实例)。 您不能保证通过保证任何一个节点具有有效类型; 你只能通过保证任何树补丁产生一个有效的来做到这一点。 如果树操作是原子的(例如,“更换节点”,“替换子节点”,“替换父节点”)并且单独应用,则这很难做到; 经过几个这样的步骤,你究竟能对这棵树说什么呢?

使用一种树重写事务更好地完成,例如,语法结构对语言L有效的源到源转换,并且应用在对该转换有效的地方。

大多数标准程序转换系统都这样做。 他们通过为L保持语法模型来实现这一点,并检查所提出的变换是否是良好类型的。 这确保了语言L到语言L的转换保持良好的形式。

如果转换从一种语言A映射到另一种语言B,则很难做到正确; 如果应用了某些此类转换,您通常会得到一个混合类型的树,这种树在任何一种语言中都是不合法的。 小心翼翼地,可以定义一组转换,将语言A的所有子树映射到语言B,并将其彻底应用; 那么你希望得到的树能够很好地形成B.你可以通过坚持在混合树中插入B-patch,如果它与另一个B-patch相邻,那么得到的化合物B-patch很好形成。 这可以使用相同风格的语法检查。

使用这些想法,您可以构建一个系统,通过一系列“表示”(langauges A,B,C,....)来映射AST,并且相信结果树的形状很好。 这个想法概括为图形重写。

===============>>#2 票数:3

这是一个基于类型安全的boost::variant AST的快速刺。

我包含了一个简单的“结构保留变换”,它只是改变了每个AST节点中存储的数据类型。 但是,理论上,您可以编写一个任意的astFunc ,它既可以对节点进行基于结构和数据的转换 - 只需编写一个type_list ,其中包含每个节点之前和之后的有效类型。

template<typename... Ts>
struct type_list {};

// specialize data_type to store something special in your AST node:
// (by default, an entry means "the type of the data")
tempalte<typename T>
struct data_type { typedef T type; };
template<typename T>
using DataType = typename data_type<T>::type;

template<template<typename>class F, typename typelist>
struct map_types;
template<template<typename>class F, template<typename...>L, typename... Ts>
struct map_types<F, L<Ts...>> {
  typedef L< F<Ts>... > type;
};
template<template<typename>class F, typename typelist>
using MapTypes = typename map_types<F, typelist>::type;

template<template<typename...>class F, typename typelist>
struct apply_list;
template<template<typename...>class F, template<typename...>class L, typename... Ts>
struct apply_list<F, L<Ts...>> {
  typedef F<Ts...> type;
};
template<template<typename...>class F, typename typelist>
using ApplyList = typename apply_list<F, typelist>::type;
template<typename typelist>
using Var = ApplyList< boost::variant, MapTypes<DataType, typelist> >;

template<typename type_list>
struct AST_Node {
  typedef std::unique_ptr<AST_Node> upAST_Node;
  std::vector<upAST_Node> children;
  Var<type_list> data;
  template<typename T>
  AST_Node( T&& t ):data( std::forward<T>(t) ) {}
};
template<typename type_list>
using upAST_Node = typename AST_Node<type_list>::upAST_Node;

template<typename before_types, typename after_types>
using typeFunc = std::function< Var<after_types>(Var<before_types>) >;

template<typename before_types, typename after_types>
using astFunc = std::function< upAST_Node<after_types>(upAST_Node<before_types>) >;

template<typename before_types, typename after_types>
astFunc<before_types, after_types> elementWiseTransform( typeFunc<before_types, after_types> func ) {
  return [func]( upAST_Node<before_types> before )->upAST_Nodes<after_types> {
    upAST_Node<after_types> after( new AST_Node<after_types>( func( before ) ) );
    after->children.reserve( before->children.size() );
    for( auto& child: before->children ) {
      after->children.push_back( elementWiseTransform(func)(std::move(child)) );
    }
    return after;
  };
}

现在这只是一个开始。

您可以更进一步,让每种类型的节点都有不同类型的子节点,甚至是不同的数字。 只需为我的data_type创建每种类型节点的traits类,例如children_types 然后使用类似的技术来定义Var以定义子类型。 基本上,你有一个variantstd::vector< AST_Node<ChildType<type_list_element>>>经由的链接MapTypes 你可以将孩子的std::vectordata捆绑在一起变成一个变种。

这将允许您为单个AST_Node类型(它生成另一个AST_Node类型)编写映射,将它们聚合在一起并生成一个AST_Node<before, after> functor然后遍历树。 一些仿函数只对数据进行操作,然后让父逻辑接管子节点,有些会转换整个子树,有些会操作数据并阻止父逻辑运行子节点。

这种技术变得棘手,因为你必须以不需要将它们全部堆叠在一起的方式从你的各个函数中合成boost变体访问者。 如果你看看这里你会看到如何采取了一堆的一些技巧std::function<T(U)> ,把它们变成一个仿函数,它利用联盟中的任何一个U 抛弃一些工作来计算返回类型的并集( type_list了重复类型的简单type_list ,然后泵入boost::variant ,可能会这样做) - 这样的“合并仿函数”将是一个有效的访问者。

现在你可以编写“重新映射一个类型为operator_add的AST节点”仿函数,并“重新映射一个类型为operator_mult的AST节点”,还有其他几个,将它们组合成一个巨型仿函数,将它们抛向AST遍历算法,并且让它喷出一个AST树,其中一些类型转换为其他类型......

但那将是很多工作。

哦,我们可能想要“阶段标记”,其中阶段1和阶段2 AST是不同类型。 我们可以用type_list标记type_list每个类型,或者我们可以只标记AST树本身。 哎呀,我们可以使用其他未使用的struct来命名AST的阶段,并通过阶段定义一个阶段,作为类型的类型,在astFunc<before_phase, before_types, after_phase, after_types>的签名中应用和强制执行。

所以这还不错。 我们创建一个节点类型的type_list 这些类型不必是存储的实际数据。 但它可以。

我们创建了一个data_type traits类,它将每个节点类型映射到存储的数据。 我们创建了一个child_types traits类,它将每个节点类型映射到子AST的type_list。

每个AST_Node存储variant<AST_Concrete_Node<Ts>...> AST_Concrete_Node包含DataType<T> data; 和一个MapTypes< std::vector, MapTypes< AST_Node, ChildTypes<T> > > children; (又名, std::vector< AST_Node<ChildrenTypes...> > ,但你不能直接说出来)。

接下来, AST_Concrete_Node<T>转换函数在一个模板元编程的棘手位置连接在一起,形成boost变体访问者。 这一步非常棘手,但我认为可行。 进行了额外的工作,以便跳过未提及的类型,因此我们不必经常说“哦,我们不想转换节点X”,而是必须说“如果我们命中节点Y,不要改变它的孩子“。

在这一点上,我会说我在喋喋不休 - 以前没有这样做过,在这种混乱的体操类型的具体实施中遇到的问题将压倒我抽象地推理它的能力。 但是这个想法很有用 - 我们有类型安全的节点类型转换,我们将它们聚合在一起并生成类型安全的树转换。 树不仅仅是一个通用变体的抽象树,而是一个树,每个节点都知道它的子节点允许的类型,它们递归地知道它们。 我们甚至可以处理“这必须有3个孩子,第一个是int ,第二个是Bob ,第三个是double ”,如果我们走得远远不够兔子洞。

  ask by Dylan translate from so

未解决问题?本站智能推荐:

1回复

了解ETF语法和抽象语法树

我将以下问题附在答案上。 我的问题是我听不懂。 您是否可以通过导出第一个表达式来提供有关解析树和ETF语法的详细整体解释? 请尝试解释第一个表达式a + b / c + d。 我认为这并不难,但是我只是无法找到合适的资源来理解这一点。 您还可以提供解释ETF语法的资源吗? 如果
5回复

解析树和抽象语法树之间有什么区别?

我在编译器设计书中找到了这两个术语,我想知道每个术语代表什么,以及它们是如何不同的。 我在互联网上搜索,发现解析树也称为具体语法树(CST)。
5回复

解析树和AST有什么区别?

它们是由编译过程的不同阶段产生的吗? 或者它们只是同一个东西的不同名称?
1回复

混淆AST函数声明的抽象格式

我正在用C ++实现编程语言,我正在进入AST生成阶段。 我想使用三步程序: 承认声明的类型; 将标记与lvalues rvalues和节点中的表达式分离为临时和本地AST; 设计并将其添加到全局AST。 以下是为变量声明提供的内容,例如: 临时表
3回复

AST在访问者或节点中遍历?

更新接受了Ira Baxter的回答,因为它指出了我正确的方向:我首先通过开始编译阶段的实现来弄清楚我实际需要什么,很明显很快,节点内的遍历使得这是一种不可能的方法。 并非所有节点都应该被访问,其中一些节点的顺序相反(例如,首先是赋值的rhs,因此编译器可以检查类型是否与rhs /运算符匹配)
1回复

使用Bison构建AST

我正在与Bison合作为我正在编写的编译器构建AST。 在AST中构建节点的最佳方法是什么? 我的问题可能更清楚一个例子。 给出以下代码段: 在上面的例子中,我希望字段规则返回一个Field节点,但我需要在解析过程中传递的修饰符规则的一些属性(即这些是合成属性)。 在不改
2回复

在C中表示抽象语法树

我正在C中为一个简单的玩具语言实现一个编译器。我有一个工作的扫描器和解析器,以及关于AST的概念功能/构造的合理背景。 我的问题与在C中表示AST的具体方式有关。我在网上不同的文本/资源中经常遇到三种风格: 每种类型的节点一个结构。 它有一个基节点“class”(struct),它
1回复

在scala内搜索2.10 AST

在scala 2.10 AST中递归搜索元素的最佳方法是什么? 树可能是power.trees(code)或mirror.mkToolBox().parseExpr(code) 编辑的结果 。 在2.10.0-RC1中, parseExpr已重命名为parse 。 我具体的用例是通
4回复

来自ANTLR Parse Tree的Python AST?

我发现了一个ANTLRv4 Python3语法 ,但它生成了一个解析树,它通常有许多无用的节点。 我正在寻找一个已知的包从该解析树中获取Python AST。 这样的事情存在吗? 编辑:澄清使用Python ast包:我的项目是Java,我需要解析Python文件。 编辑
1回复

为什么不显示分析树?

我有一个ANTLR语法,并尝试制作一个解析树以在Java中使用jFrame进行显示。 但是,解析树未显示,如下所示。 我确实注意到,如果我删除了对访客行Object answer = new ExpAnalyserBaseVisitor<>().visit(parser.e