我正在从事一个项目,该项目涉及在 Java 的一个非常小的子集中优化某些结构,并以 BNF 形式化。
如果我要在 Java 中执行此操作,我会使用 JTB 和 JavaCC 的组合来构建 AST。然后使用访问者来操纵树。但是,鉴于 Haskell 中有大量用于解析的库(parsec、happy、alex 等),我在选择合适的库时有点困惑。
所以,简单地说,当在 BNF 中指定一种语言时,哪个库提供了构建 AST 的最简单方法?在惯用的 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
(scrap your Boilerplate),可以让您编写非常简洁的树遍历和修改。lens
也是一种选择。至少签出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
所有内容JavaClasses
。这也可以在lens
许多其他库中执行,但syb
非常容易阅读。
我从未使用过bnfc-meta
(@phg 建议),但我强烈建议您查看BNFC(关于 hackage:http ://hackage.haskell.org/package/BNFC )。基本方法是您以带注释的 BNF 样式编写语法,它会自动为语法生成 AST、解析器和漂亮的打印机。
BNFC 的适用程度取决于语法的复杂程度。如果它不是上下文无关的,那么您可能很难取得任何进展(我确实在破解上下文相关扩展方面取得了一些成功,但该代码现在可能有点腐烂了)。另一个缺点是您的 AST 将非常直接地反映语法规范。但是由于您已经有了 BNF 规范,为 BNFC 添加必要的注释应该相当简单,因此这可能是获得可用 AST 的最快方法。即使您决定走不同的路线,您也可以将生成的数据类型作为手写版本的起点。
亚历克斯+快乐。
有许多方法可以修改/调查已解析的术语 (AST)。要搜索的关键字是“数据类型通用”编程。但请注意:这是一个复杂的话题......
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
由于您的语法可以用 BNF 表示,因此它属于可以使用 shift-reduce 解析器(LALR 语法)有效解析的语法。这种高效的解析器可以由解析器生成器 yacc/bison (C,C++) 或其 Haskell 等效的“Happy”生成。
这就是为什么我会在你的情况下使用“快乐”。它采用 BNF 形式的语法规则,并直接从中生成解析器。生成的解析器将接受语法规则描述的语言并生成 AST(抽象语法树)。Happy 用户指南非常好,可以让您快速入门: http ://www.haskell.org/happy/doc/html/
要转换生成的 AST,泛型编程是一个好主意。这是关于如何在 Haskell 中以实用的方式从头开始执行此操作的经典解释: http ://research.microsoft.com/en-us/um/people/simonpj/papers/hmap/
我正是使用它来为小型领域特定语言构建编译器,这是一个简单而简洁的解决方案。
您还可以查看 Haskell 编译器系列,它很好地介绍了使用 alex 并乐于解析 Java 的子集:http: //bjbell.wordpress.com/haskell-compiler-series/。