说我有两种语言(A& B)。我的目标是编写某种类型的程序,将A中的语法转换为B的等价物。目前我的解决方案是使用Haskell的Parsec来执行此任务。作为对Haskell不熟悉的人以及针对此问题的函数式编程,在Parsec中找到一个简单的例子非常困难。我在网上找到的例子要么是不完整的例子(令新的Haskell程序员感到沮丧),要么从我的目标中删除太多。
有人能为我提供一个非常简单明了的例子,让我使用Parsec来实现与我想要实现的目标相关的事情吗?或者甚至是一些与我的目标相似的教程。
感谢。
答案 0 :(得分: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
- 表示法。在do
之后,接下来是一系列语句。对于解析器,这些语句只是其他解析器。可以使用<-
绑定解析器的结果。这样,通过链接多个较小的解析器来构建一个大的解析器。为了获得有趣的效果,还可以使用特殊组合器(例如a <|> b
)组合解析器,该组合解析a
或b
或many a
,解析a
try
尽可能)。请注意,Parsec默认情况下不会回溯。如果解析器在使用字符后可能会失败,请在其前面添加try
以启用一个实例的回溯。 csv
减慢了解析速度。
结果是解析器xsvToTSV :: XSV -> String
xsvToTSV xst = unlines (map toLines xst) where
toLines = intersperse '\t'
,它将我们的CSV文档解析为抽象语法树。现在很容易将其转换为另一种语言(例如TSV):
csvToTSV :: String -> Maybe String
csvToTSV document = case parse csv "" document of
Left _ -> Nothing
Right xsv -> xsvToTSV xsv
连接这两件事会获得转换功能:
{{1}}
就是这样! Parsec有很多其他函数来构建极其复杂的解析器。这本书Real World Haskell有一个很好的关于解析器的章节,但它有点过时了。但大多数情况仍然如此。如果您还有其他问题,请随时提出。