[英]Parsing Haskell custom data types
我已经通过这里提供的Haskell Koans工作: https : //github.com/roman/HaskellKoans
我被困在最后两个Koans上,都涉及解析自定义代数数据类型。 这是第一个:
data Atom = AInt Int | ASym Text deriving (Eq, Show)
testAtomParser :: Test
testAtomParser = testCase "atom parser" $ do
-- Change parser with the correct parser to use
--
let parser = <PARSER HERE> :: P.Parser Atom
assertParse (ASym "ab") $ P.parseOnly parser "ab"
assertParse (ASym "a/b") $ P.parseOnly parser "a/b"
assertParse (ASym "a/b") $ P.parseOnly parser "a/b c"
assertParse (AInt 54321) $ P.parseOnly parser "54321"
如何定义变量解析器,以便它可以解析代数数据类型Atom
来传递断言?
ADT的解析器倾向于反映ADT的形状。 你的ADT是由两个不相交的部分组成的,所以你的解析器也可能有两个不相交的部分
atom = _ <|> _
假设我们知道如何解析单个数字(让我们称之为基本解析器digit
),那么我们通过重复它来解析(非负)整数。
natural = let loop = digit >> loop in loop
这成功地解析了无限的数字流并将它们抛弃。 我们可以做得更好吗? 不幸的是,不仅仅是一个monad实例,我们需要另一个基本的组合器, many
,它修改了一些其他解析器以消耗输入0次或更多次,将结果累积到列表中。 我们实际上会稍微调整一下,因为空解析不是有效数字
many1 p = do x <- p
xs <- many p
return (x:xs)
natural' = many1 digit
原子怎么样? 为了传递测试用例,似乎原子必须是1对多的字母数字字符或反斜杠。 同样,这个不相交的结构可以立即在我们的解析器中表达
sym = many1 (_ <|> _)
我们将再次使用一些内置的简单解析器组合来构建我们想要的东西,比如satisfy :: (Char -> Bool) -> Parser Char
匹配任何满足某些谓词的字符。 我们可以立即构建另一个有用的组合器, char c = satisfy (==c) :: Char -> Parser Char
然后我们就完成了。
sym = many1 (char '/' <|> satisfy isAlpha)
其中isAlpha
是一个谓词,就像正则表达式[a-zA-Z]
。
所以现在我们有了解析器的核心
natural <|> sym :: Parser String
many1
组合器将我们的字符解析器提升为字符列表的解析器( String
s!)。 这个提升动作也是构建ADT解析器的基本思路。 我们想把我们的Parser String
升级为Parser Atom
。 一种方法是使用函数toAtom :: String -> Atom
然后我们可以将其fmap
到Parser
atom' :: Parser Atom
atom' = fmap toAtom (natural <|> sym)
但是类型为String -> Atom
的函数首先会破坏构建解析器的目的。
如I.中所述,重要的部分是ADT的形状反映在我们的atom
解析器的形状中。 我们需要利用它来构建我们的最终解析器。
我们需要利用atom
解析器结构中的信息。 让我们构建两个函数
liftInt :: String -> Atom -- creates `AInt`s
liftSym :: String -> Atom -- creates `ASym`s
liftInt = AInt . read
liftSym = ASym
每一个都说明了将String
转换为Atom
的方法,同时也声明了我们正在处理的是什么类型的Atom
。 值得注意的是,如果我们传递一个无法解析为Int
的字符串, liftInt
将抛出运行时错误。 幸运的是,这正是我们所知道的。
atomInt :: Parser Atom
atomInt = liftInt <$> natural
atomSym :: Parser Sym
atomSym = liftSym <$> sym
atom'' = atomInt <|> atomSym
现在我们的atom''
解析器利用了natural
只返回有效解析的字符串的保证 - 我们对read
的调用不会失败!---我们尝试按顺序构建AInt
和ASym
,像ADT的结构一样,在一个不相交的结构中一个接一个地尝试。
整个社会就是这样
atom''' = AInt . read <$> many1 digit
<|> ASym <$> many1 ( char '/'
<|> satisfy isAlpha)
这展示了解析器组合器的乐趣。 整个事物是使用小巧,可组合的简单部件从地面构建的。 每个人都做了一个非常小的工作,但他们一起跨越了大量的解析器。
您还可以使用ADT中的更多分支,更完整指定的符号类型解析器或使用<?>
故障装饰轻松扩充此语法,以便在失败的分析中包含大量错误消息。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.