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



假设我有两种语言(A&B)。我的目标是编写某种类型的程序,将A中的语法转换为等效的B。目前,我的解决方案是使用Haskell的Parsec来执行这项任务。然而,作为Haskell和函数编程的新手,在Parsec中只找到一个简单的例子是相当困难的。我在网上找到的例子要么是不完整的例子(对于一个新的Haskell程序员来说很沮丧),要么离我的目标太远了。

那么,有人能为我提供一个令人惊讶的琐碎而明确的例子,让我使用Parsec来实现我想要实现的目标吗?或者甚至是一些与我的目标相似的教程。

谢谢。

考虑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-表示法。在do之后,将出现一系列语句。对于解析器来说,这些语句只是其他的解析器。可以使用<-来绑定解析器的结果。通过这种方式,可以通过链接多个较小的解析器来构建一个大型解析器。为了获得有趣的效果,还可以使用特殊的组合子组合解析器(例如a <|> b,它解析abmany 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还有许多其他功能来构建极其复杂的解析器。《真实世界哈斯克尔》一书中有一章很好地介绍了解析器,但有点过时了。不过,大部分情况仍然是真的。如果您还有其他问题,请随时提问。

最新更新