我正在使用scala编写玩具编译器。目标语言本身看起来像scala,但它是一个开放的实验领域。
经过几次大型重构之后,我找不到一种模拟抽象语法树的好方法。我想使用scala模式匹配的设施,问题是树在编译过程中携带移动信息(如类型,符号)。
我可以看到几个解决方案,其中没有一个我喜欢:
具有可变字段的案例类(我相信scala编译器会这样做):问题是这些字段不存在于编译的每个阶段,因此必须被置为空(或者选项)并且它调试/编写代码变得非常繁重。此外,如果例如,我在键入阶段后找到一个空类型的节点,我很难找到错误的原因。
巨大的特质/案例类层次结构:像Node,NodeWithSymbol,NodeWithType,......似乎很难写和使用
完全用提取器手工制作的东西
我也不确定使用完全不可变的AST是否是一个好习惯,特别是在scala中没有隐式共享(因为编译器不知道不变性)并且它可能会损害复制树的性能一直以来。
你能想到使用scala强大的类型系统来模拟树的优雅模式吗?
答案 0 :(得分:11)
TL; DR我更喜欢保持AST不可变并在类型信息中携带类似信息,例如一个Map,可以由存储在AST中的ID引用。但是没有完美的答案。
你绝不是第一个与这个问题斗争的人。我列举一些选项:
1)在每个阶段都得到更新的可变结构。你提到的所有起伏不定。
2)特征/蛋糕模式。可行,但价格昂贵(没有共享),有点难看。
3)每个阶段的新树类型。在某些方面,这是理论上最干净的。每个阶段只能处理前一阶段为其生成的结构。此外,同样的方法从前端到后端都是一路进行的。例如,您可能在某些时候“desugar”并且具有新的树类型意味着下游阶段甚至不必考虑通过去除消除的节点类型的可能性。此外,低级优化通常需要比原始AST低得多的IR。但这也是很多代码,因为几乎所有东西都必须在每一步都重新创建。这种方法也可能很慢,因为阶段之间几乎没有数据共享。
4)使用ID标记AST中的每个节点,并使用该ID引用其他数据结构(映射和向量等)中的信息,这些信息保存为每个阶段计算的信息。在很多方面,这是我的最爱。它保留了不变性,最大化共享并最小化您必须编写的“多余”代码。但是你仍然需要处理可能难以调试的“丢失”信息的可能性。它也没有可变选项那么快,但比任何需要在每个阶段生成新树的选项都要快。
答案 1 :(得分:5)
我最近开始为一种小语言编写一个玩具验证程序,我正在使用Kiama库来解析器,解析器和类型检查器阶段。
Kiama是一个用于语言处理的Scala库。它可以方便地分析和转换结构化数据。库支持的编程风格基于众所周知的正式语言处理范例,包括attribute grammars,tree rewriting,abstract state machines和pretty printing。
我会尝试总结一下(相当有限的)经验:
[+] Kiama附带了几个例子,主要撰稿人通常会对邮件列表中提出的问题做出快速回应
[+]属性语法范例允许很好地分离节点的“不可变组件”,例如名称和子节点,以及“可变组件”,例如类型信息
[+]该库附带了一个多功能的重写系统 - 到目前为止 - 涵盖了我的所有用例
[+]图书馆,例如漂亮的打印机,提供了很好的DSL和各种功能模式/方法/想法的例子
[ - ]学习曲线非常陡峭,即使有示例和手头的邮件列表
[ - ]以“纯函数”的方式实现解析阶段(参见my question)似乎很棘手,但混合方法(我还没有尝试过)似乎是可能的< / p>
[ - ]属性语法范例和由此产生的关注点分离并不能明确如何记录节点最终的属性(参见my question)
[ - ]有传言说,属性语法范式没有产生最快的实现
总结我的总结,我很喜欢使用Kiama,我强烈建议你试一试,或者至少看看这些例子。
(PS。我与Kiama无关)