2009-11-08

用Haskell写个JSON解析器

本想自力更生试着用Haskell写个JSON解析器,一不小心网上一搜一大把,并且忍不住瞄了几眼。那就做个简单的修改加翻译吧,原文链接:http://snippets.dzone.com/posts/show/3660

> import Text.ParserCombinators.Parsec
> import System
> import qualified Data.Map as Map


引入一些必要的库,比如Parsec,祭起我们的利器哈!

> mainParser = do {
>               val <- valueParser
>             ; skipMany space
>             ; eof
>             ; return val
>             }


解析器的主函数,该函数以典型的Monad风格对输入数据调用valueParser函数进行解析,do中的操作是序列华的,每一个行都是一次匹配,这三行的意思是,它期待的输入的格式是JSON数据、可能的一些空字符、文件尾巴,如果解析成功就返回解析后的数据val。

> main :: IO ()
> main = do {
>         args <- getArgs
>       ; val <- parseFromFile mainParser $ args !! 0
>       ; print val
>       }


main函数先得到命令行参数存在列表args中,列表的第一个元素(args !! 0)作为文件名,parserFromFile是Parsec里的一个函数,它调用mainParser对命令行指定的文件进行解析,最后在打印出解析结果val。

> data JSON = ListValue [JSON]
>           | LiteralString String
>           | LiteralInt Integer
>           | LiteralBoolean Bool
>           | RecordValue (Map.Map String JSON)
>             deriving Show


上面的这些玩意儿叫做ADT,它的意思是:我们有JSON这样一种数据结构,它有五个构造函数ListValue, LiteralString, LiteralInt, LiteralBoolean和RecordValue,每个构造函数后面跟着的都是一种类型。最后,该数据结构继承所有Show类具有的行为 -- 这使得JSON类型的数据可以用print显示出来。

有了上面的定义后,我们可以方便的构造出一些JSON类型的数据,比如:

LiteralString "abc"
LiteralInt 123


在GHC或者Hugs中可以用:t来显示给定输入的类型:

:t LiteralString "abc"
LiteralString "abc" :: JSON


这是说LiteralString "abc"是个JSON类型。好了,接下来写几个简单的parser,我们可以dive & conquer。第一个Parser用来识别字符串 -- 字符串以'"'开头,中间是一个或者多个字符,最后有个'"'收尾。解析成功后会返回一个JSON类型的字符串。
> literalString :: Parser JSON
> literalString = do {
>     char '"'
>     ; val <- many1 letter
>     ; char '"'
>     ; return $ LiteralString val
>     }


接下来雷同的便是解析整型、布尔型:
> literalInt :: Parser JSON
> literalInt = do {
>     ; val <- many1 digit
>     ; return $ LiteralInt (read val)
>     }

>
> literalBoolean :: Parser JSON
> literalBoolean =
>         do {
>           string "true"
>         ; return $ LiteralBoolean True
>         }
>     <|> do {
>         string "false"
>         ; return $ LiteralBoolean False
>         }

这里'<|>'是个combinator,它用来连接两个parser,如果前一个解析不成功,就用下一个来解析。用'<|>'可以把整个JSON的解析写成如下形式:
> valueParser :: Parser JSON
> valueParser =
>      literalString
>  <|> literalInt
>  <|> literalBoolean
>  <|> recordParser
>  <|> listParser


其中还有两个parser没有实现:recordParser和listParser,分别用来解析object和array。list以'['打头,']'结尾,其中的数据以','分割:
> listParser :: Parser JSON
> listParser = do {
>     char '['
>     ; words <- sepBy1 valueParser listSeparator
>     ; char ']'
>     ; return $ ListValue words
>     }

>
> listSeparator :: Parser ()
> listSeparator = do {
>     skipMany space
>     ; char ','
>     ; skipMany space
>     }

最后就是解析object啦,打完收功!
> recordParser :: Parser JSON
> recordParser = do {
>     char '{'
>     ; defs <- endBy definitionParser listSeparator
>     ; char '}'
>     ; return $ RecordValue $ Map.fromList defs
>     }

>
> definitionParser :: Parser (String, JSON)
> definitionParser = do {
>     skipMany space
>     ; key <- many1 letter
>     ; char ':'
>     ; skipMany space
>     ; val <- valueParser
>     ; return (key, val)
>     }
>
> definitionSeparator :: Parser ()
> definitionSeparator = do {
>     skipMany space
>     ; char ','
>     ; skipMany space
>     ; return ()
>     }

标签:

0 Comments:

发表评论

<< Home