如何从Haskell中的语法规范构建抽象语法树?

时间:2013-09-10 11:27:15

标签: haskell abstract-syntax-tree parsec happy zipper

我正在开发一个项目,该项目涉及在一小部分Java中优化某些构造,在BNF中形式化。

如果我在Java中这样做,我会使用构建AST的JTB和JavaCC的组合。然后访客用于操纵树。但是,考虑到在Haskell中解析庞大的库(parsec,happy,alex等),我在选择合适的库时有点困惑。

所以,简单地说,当在BNF中指定一种语言时,哪个库提供了构建AST的最简单方法?在惯用的Haskell中修改这个树的最佳方法是什么?

5 个答案:

答案 0 :(得分:7)

在Haskell中有两种主要的解析方法,解析组合器或解析器生成器。既然你已经有了BNF,我建议使用后者。

好的是alex。 GHC的解析器IIRC是用这个编写的,所以你会很好地合作。

接下来,您将有一大堆数据声明要解析为:

data JavaClass = {
    className :: Name,
    interfaces :: [Name],
    contents :: [ClassContents],
    ...
 }
  data ClassContents = M Method
                     | F Field
                     | IC InnerClass

以及表达式和其他任何你需要的东西。最后,你将把它们组合成像

这样的东西
data TopLevel = JC JavaClass
              | WhateverOtherForms
              | YouWillParse

一旦你有了这个,你将整个AST表示为一个TopLevel或它们的列表,具体取决于你解析的类/文件的数量。

从这里开始取决于你想做什么。有许多库,例如syb(废弃样板),可以让您编写非常简洁的树遍历和修改。 lens也是一种选择。至少请查看Data.TraversableData.Foldable

要修改树,您可以执行简单的操作

ignoreInnerClasses :: JavaClass -> JavaClass
ignoreInnerContents c = c{contents = filter isClass $ contents c}
 --                           ^^^ that is called a record update
    where isClass (IC _) = True
          isClass _      = False

然后您可能会使用syb之类的东西来编写

 everywhere (mkT ignoreInnerClass) toplevel

将遍历所有内容并将ignoreInnerClass应用于所有JavaClasses。这可以在lens和许多其他库中执行,但syb非常容易阅读。

答案 1 :(得分:4)

Alex + Happy。

有许多方法可以修改/调查解析的术语(AST)。要搜索的关键字是“datatype-generic”编程。但要注意:这是一个复杂的话题......

http://people.cs.uu.nl/andres/Rec/MutualRec.pdf

http://www.cs.uu.nl/wiki/GenericProgramming/Multirec

它有一个拉链的通用实现:

http://hackage.haskell.org/packages/archive/zipper/0.3/doc/html/Generics-MultiRec-Zipper.html

同时结帐https://github.com/pascalh/Astview

答案 2 :(得分:4)

我从未使用bnfc-meta(由@phg建议),但强烈建议您查看BNFC(关于hackage:http://hackage.haskell.org/package/BNFC)。基本方法是用带注释的BNF样式编写语法,它会自动为语法生成AST,解析器和漂亮的打印机。

BNFC的合适程度取决于语法的复杂程度。如果它不是没有上下文的,那么你可能很难取得任何进展(我确实成功地破解了上下文敏感的扩展,但是这些代码现在可能有点烂了)。另一个缺点是你的AST将直接反映语法规范。但由于您已经有了BNF规范,因此为BNFC添加必要的注释应该相当简单,因此它可能是获得可用AST的最快方法。即使您决定采用不同的路线,也可以将生成的数据类型作为手写版本的起点。

答案 3 :(得分:2)

您也可以查看Haskell编译器系列,它很好地介绍了使用alex并且很乐意解析Java的一个子集:http://bjbell.wordpress.com/haskell-compiler-series/

答案 4 :(得分:1)

由于您的语法可以用BNF表示,因此它在使用shift-reduce解析器(LALR语法)有效解析的语法类中。这种有效的解析器可以由解析器生成器yacc / bison(C,C ++)或其Haskell等效的“Happy”生成。

这就是为什么我会在你的情况下使用“快乐”。它采用BNF形式的语法规则,并直接从中生成解析器。生成的解析器将接受语法规则描述的语言并生成AST(抽象语法树)。快乐的用户指南非常好,让您快速入门: http://www.haskell.org/happy/doc/html/

要转换生成的AST,通用编程是一个好主意。以下是从头开始以实用的方式在Haskell中执行此操作的经典解释: http://research.microsoft.com/en-us/um/people/simonpj/papers/hmap/

我已经用它来构建一个针对小型域特定语言的编译器,这是一个简单而简洁的解决方案。