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

Vin*_*sso 6 parsing interpreter haskell parsec

说我有两种语言(A和B).我的目标是编写某种类型的程序,将A中的语法转换为B的等价物.目前我的解决方案是使用Haskell的Parsec来执行此任务.作为对Haskell不熟悉的人以及针对此问题的函数式编程,在Parsec中找到一个简单的例子非常困难.我在网上找到的例子要么是不完整的例子(对于一个新的Haskell程序员而言令人沮丧),要么从我的目标中删除太多.

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

谢谢.

fuz*_*fuz 16

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

csv   = *crow
crow  = *(ccell ',') ccell CR
ccell = "'" *(ALPHA / DIGIT) "'"
Run Code Online (Sandbox Code Playgroud)

我们想编写一个转换器,将该语法转换为TSV(制表符分隔值)文档:

tsv   = *trow
trow  = *(tcell HTAB) tcell CR
tcell = DQUOTE *(ALPHA / DIGIT) DQUOTE
Run Code Online (Sandbox Code Playgroud)

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

data XSV  = [Row]
type Row  = [Cell]
type Cell = String
Run Code Online (Sandbox Code Playgroud)

为这个语法编写解析器非常简单.我们编写一个解析器就好像我们要描述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
Run Code Online (Sandbox Code Playgroud)

此解析器使用do-notation.在a之后do,接下来是一系列陈述.对于解析器,这些语句只是其他解析器.可以<-用来绑定解析器的结果.这样,通过链接多个较小的解析器来构建一个大的解析器.为了获得有趣的效果,还可以使用特殊的组合程序(如结合分析器a <|> b,它解析要么ab或者many a,它解析为许多aS作为可能的).请注意,Parsec默认情况下不会回溯.如果解析器在使用字符后可能会失败,请在其前面添加try以启用一个实例的回溯.try减慢解析速度.

结果是一个解析器csv,它将我们的CSV文档解析为抽象语法树.现在很容易将其转换为另一种语言(例如TSV):

xsvToTSV :: XSV -> String
xsvToTSV xst = unlines (map toLines xst) where
  toLines = intersperse '\t'
Run Code Online (Sandbox Code Playgroud)

连接这两件事就可以获得转换功能:

csvToTSV :: String -> Maybe String
csvToTSV document = case parse csv "" document of
  Left _    -> Nothing
  Right xsv -> xsvToTSV xsv
Run Code Online (Sandbox Code Playgroud)

这就是全部!Parsec有很多其他函数来构建极其复杂的解析器.Real World Haskell这本书有一个关于解析器的好章节,但它有点过时了.但大多数情况仍然如此.如果您还有其他问题,请随时提出.

  • 请注意,此代码未经测试.如果它不起作用,请责怪我,并且对于什么不起作用要冗长. (2认同)