使用Haskell的Parsec进行编程语言转换器

6
我有两种语言(A和B),我的目标是编写一些程序,将在A中找到的语法转换为B的等效语法。目前,我的解决方案是使用Haskell的Parsec来执行此任务。然而,作为一个新手,对于Haskell和函数式编程来说,找到一个简单的Parsec示例已经非常困难了。我在网上找到的示例要么是不完整的示例(对于新手Haskell程序员来说很令人沮丧),要么与我的目标相去甚远。
所以,有人能提供给我一个关于使用Parsec实现我想要实现的内容的非常简单和明确的例子吗?或者甚至是一些教程,与我的目标相似。
谢谢。

1
我理解你的痛苦。如果你不想混淆解析和词法分析,你可能也想调查一下Alex(http://www.haskell.org/alex/doc/html/introduction.html)。 - jberryman
1
请查看Real world haskell - fuz
2
人们认为拥有 AST 就足以在大多数编程语言中执行任何真正有用的操作,但这是错误的;你需要更多的机制。仅有解析器是不够的。请参见我的 SO 答案:https://dev59.com/anA75IYBdhLWcg3wJFYL#3460977。 - Ira Baxter
2
当然,正如您所观察到的那样,解析是前提条件。了解芥子气后面有个尖峰陷阱很好,但您可能仍然需要使用防毒面具来获得帮助。 - Daniel Wagner
2
我不介意人们获得防毒面具。我反对天真的想法,认为防毒面具就是一个人所需要的全部;直到他们到达时,没有人会问关于尖刺坑的问题,然后他们几乎会感到惊讶。更重要的是,在惊讶之后,他们自然而然地掉进坑里死去,除了浪费时间和宝贵的资源外,什么也没发生。问题在于,这个教训似乎并没有被社区理解。 - Ira Baxter
1个回答

16

考虑以下 CSV 文档的简单语法(ABNF 表示):

csv   = *crow
crow  = *(ccell ',') ccell CR
ccell = "'" *(ALPHA / DIGIT) "'"

我们希望编写一个将此语法转换为TSV(制表符分隔值)文档的转换器:
tsv   = *trow
trow  = *(tcell HTAB) tcell CR
tcell = DQUOTE *(ALPHA / DIGIT) DQUOTE

首先,让我们创建一个代数数据类型来描述我们的抽象语法树。类型同义词包括在内以方便理解:

data XSV  = [Row]
type Row  = [Cell]
type Cell = String

编写这个语法的解析器非常简单。我们编写一个解析器,就像我们描述ABNF一样:
csv :: Parser XSV
csv = XSV <$> many crow

crow :: Parser Row
crow = do cells <- ccell `sepBy` (char ',')
          newline
          return cells

ccell :: Parser Cell
ccell = do char '\''
           content <- many (digit <|> letter)
           char '\''
           return content

这个解析器使用 do-notation。在 do 后面,跟着一系列语句。对于解析器来说,这些语句只是其他解析器。可以使用 <- 来绑定解析器的结果。这样,通过链接多个较小的解析器,就可以构建一个大的解析器。为了获得有趣的效果,还可以使用特殊的组合器来组合解析器(例如 a <|> b,它可以解析 ab,或者 many a,它尽可能多地解析 a)。请注意,Parsec 默认不会回溯。如果一个解析器在消耗字符后可能失败,请在其前缀中加上 try 以启用回溯。但是,try 会减慢解析速度。
结果是一个解析器 csv,它将我们的 CSV 文档解析成一个抽象语法树。现在,将其转换为另一种语言(如 TSV)就很容易了。
xsvToTSV :: XSV -> String
xsvToTSV xst = unlines (map toLines xst) where
  toLines = intersperse '\t'

连接这两个事物,就得到了一个转换函数:
csvToTSV :: String -> Maybe String
csvToTSV document = case parse csv "" document of
  Left _    -> Nothing
  Right xsv -> xsvToTSV xsv

就是这样了!Parsec还有许多其他功能可以构建极其复杂的解析器。书籍Real World Haskell有一个不错的章节介绍了解析器,但它有点过时。大部分内容仍然正确。如果你有进一步的问题,请随时提出。


2
请注意,此代码未经测试。如果无法正常工作,请责怪我,并详细说明哪里出了问题。 - fuz
CSV文档中的语法中,“cell”应该改为“ccell”(第二行),是吗?您能告诉我语法中的“CR”代表什么吗?您的XSV解析器在哪里处理将每一行解析应用于文档中的每一行?谢谢! - efie
CR代表回车符;在这种情况下,它只表示换行符。换行符因操作系统而异。 - identity
解析器 crow 解析整行,包括换行符。csv 简单地尽可能多次应用 crow 解析器,直到失败或所有输入都被消耗。<$> 只是将结果包装起来。(我这样做是为了避免也可能出现的 do-notation) - fuz
请参考Wikipedia中对CR的定义。它是ABNF中预定义规则之一。 - fuz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接