simple-parser-0.2.2: src/SimpleParser/Examples/Json.hs
{-# LANGUAGE OverloadedStrings #-}
module SimpleParser.Examples.Json
( Json (..)
, JsonF (..)
, parseJson
) where
import Control.Applicative (empty)
import Control.Monad (void)
import Data.Char (isSpace)
import Data.Text (Text)
import Data.Void (Void)
import SimpleParser
-- JSON without numbers...
data JsonF a =
JsonObject ![(String, a)]
| JsonArray ![a]
| JsonString !String
| JsonBool !Bool
| JsonNull
deriving (Eq, Show, Functor, Foldable, Traversable)
newtype Json = Json { unJson :: JsonF Json } deriving (Eq, Show)
type JsonParser a = Parser Void Text a
parseJson :: Text -> [Json]
parseJson str = do
ParseResult v _ <- runParser (jsonParser <* matchEnd) str
case v of
ParseSuccess a -> pure a
jsonSpace :: JsonParser ()
jsonSpace = greedyStarParser_ (void (satisfyToken isSpace))
jsonLexeme :: JsonParser () -> JsonParser a -> JsonParser a
jsonLexeme spaceAfter thing = do
a <- thing
spaceAfter
pure a
jsonBetween :: JsonParser () -> JsonParser () -> JsonParser a -> JsonParser a
jsonBetween start end thing = do
start
a <- thing
end
pure a
jsonSepBy :: JsonParser a -> JsonParser () -> JsonParser [a]
jsonSepBy thing sep = go [] where
optThing = optionalParser thing
optSep = optionalParser sep
go !acc = do
ma <- optThing
case ma of
Nothing -> if null acc then pure [] else empty
Just a -> do
let newAcc = a:acc
ms <- optSep
case ms of
Nothing -> pure (reverse newAcc)
Just () -> go newAcc
jsonCharLexeme :: Char -> JsonParser ()
jsonCharLexeme c = void (jsonLexeme jsonSpace (matchToken c))
jsonWordLexeme :: Text -> JsonParser ()
jsonWordLexeme cs = void (jsonLexeme jsonSpace (matchChunk cs))
openBrace, closeBrace, comma, colon, openBracket, closeBracket, closeQuote :: JsonParser ()
openBrace = jsonCharLexeme '{'
closeBrace = jsonCharLexeme '}'
comma = jsonCharLexeme ','
colon = jsonCharLexeme ':'
openBracket = jsonCharLexeme '['
closeBracket = jsonCharLexeme ']'
closeQuote = jsonCharLexeme '"'
openQuote :: JsonParser ()
openQuote = void (matchToken '"')
nullTok, trueTok, falseTok :: JsonParser ()
nullTok = jsonWordLexeme "null"
trueTok = jsonWordLexeme "true"
falseTok = jsonWordLexeme "false"
nonQuoteChar :: JsonParser Char
nonQuoteChar = satisfyToken (/= '"')
nonQuoteString :: JsonParser String
nonQuoteString = greedyStarParser nonQuoteChar
rawStringParser :: JsonParser String
rawStringParser = jsonBetween openQuote closeQuote nonQuoteString
-- TODO(ejconlon) This does not handle escape codes. Use `foldTokensWhile` for that...
stringParser :: JsonParser (JsonF a)
stringParser = fmap JsonString rawStringParser
nullParser :: JsonParser (JsonF a)
nullParser = JsonNull <$ nullTok
boolParser :: JsonParser (JsonF a)
boolParser = isolateParser (branchParser [JsonBool True <$ trueTok, JsonBool False <$ falseTok])
objectPairParser :: JsonParser a -> JsonParser (String, a)
objectPairParser root = do
name <- rawStringParser
colon
value <- root
pure (name, value)
objectParser :: JsonParser (String, a) -> JsonParser (JsonF a)
objectParser pairParser = jsonBetween openBrace closeBrace (fmap JsonObject (jsonSepBy pairParser comma))
arrayParser :: JsonParser a -> JsonParser (JsonF a)
arrayParser root = jsonBetween openBracket closeBracket (fmap JsonArray (jsonSepBy root comma))
rootParser :: JsonParser a -> JsonParser (JsonF a)
rootParser root = isolateParser (branchParser opts) where
pairParser = objectPairParser root
opts =
[ objectParser pairParser
, arrayParser root
, stringParser
, boolParser
, nullParser
]
jsonParser :: JsonParser Json
jsonParser = let p = fmap Json (rootParser p) in p