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

如何从Haskell中的语法规范构建抽象语法树?,haskell,abstract-syntax-tree,parsec,happy,zipper,Haskell,Abstract Syntax Tree,Parsec,Happy,Zipper,我正在从事一个项目,该项目涉及在一个非常小的Java子集中优化某些结构,用BNF形式化 如果我在Java中这样做,我将使用JTB和JavaCC的组合来构建AST。然后,访问者被用来操纵这棵树。但是,考虑到Haskell中有大量用于解析的库(parsec、happy、alex等),我在选择合适的库时有点困惑 那么,简单地说,当在BNF中指定语言时,哪个库提供了构建AST的最简单方法?在惯用的Haskell中修改这棵树的最佳方法是什么?在Haskell中,有两种主要的解析方法,解析组合符或解析器生成

我正在从事一个项目,该项目涉及在一个非常小的Java子集中优化某些结构,用BNF形式化

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


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

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

一个好的是。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
(废弃样板文件),可以让您编写非常简洁的树遍历和修改<代码>镜头也是一个选项。至少签出
Data.Traversable
Data.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
应用于所有
javaclass
。这可以在
lens
和许多其他库中实现,但是
syb
非常容易阅读

Alex+很高兴

有许多方法可以修改/研究解析术语(AST)。要搜索的关键字是“数据类型通用”编程。但要注意:这是一个复杂的话题

这里提供了拉链的通用实现:


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


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

您还可以查看Haskell编译器系列,该系列很好地介绍了如何使用alex并乐于解析Java的子集:。

因为您的语法可以用BNF表示,它属于可以使用移位减少解析器(LALR语法)有效解析的语法类。这种高效的解析器可以由解析器生成器yacc/bison(C,C++)或其Haskell等价物Happy生成

这就是为什么我会在你的情况下使用“快乐”。它采用BNF形式的语法规则,并直接从中生成解析器。生成的解析器将接受语法规则描述的语言,并生成AST(抽象语法树)。《快乐用户指南》相当不错,让您快速入门:

要转换生成的AST,泛型编程是一个好主意。以下是关于如何在Haskell中从零开始以实用的方式实现这一点的经典解释:


我正是利用这一点为一种小型领域特定语言构建了一个编译器,这是一个简单而简洁的解决方案。

还有一种更酷、更具实验性的可能性,即使用模板haskell从源代码中提供的BNF构造解析器,称为。不过,我不知道它在实际应用中有多有用。(如果您是Haskell的新手…)在Haskell或其他现代函数语言(如SML和Caml)中没有必要使用JTB这样的树生成器。代数类型与JTB或类似工具的树规范一样简洁——它们是用函数语言定义数据类型的标准方法。在Haskell中,您还可以自动派生数据类型上的许多有用函数,如显示,序列化和结构平等。有两种高级工具用于AST操作和优化:
uuagc
属性语法的预处理器和
hoopl
用于基于可组合事实格的优化的通用库。如果您有BNF,这绝对是最简单的方法!数据类型泛型编程看起来确实非常有趣。我试试看。我喜欢清楚的答案。尽管我只处理一个子集,但从头开始编写数据类型的工作太多了。然而,我计划使用syb进行转换。