5.8 KiB
5.8 KiB
JSON Parser
module Parser.JSON
import public Parser
import public Parser.Numbers
import Structures.Dependent.DList
JSON components
Types a JSON value is allowed to have
public export
data JSONType : Type where
TObject : JSONType
TArray : JSONType
TString : JSONType
TNumber : JSONType
TBool : JSONType
TNull : JSONType
%runElab derive "JSONType" [Generic, Meta, Eq, Ord, Show, DecEq]
%name JSONType type, type2, type3
A JSON value indexed by its type
public export
data JSONValue : JSONType -> Type where
VObject : {types : List JSONType}
-> DList JSONType (\t => (String, JSONValue t)) types -> JSONValue TObject
VArray : {types : List JSONType}
-> DList JSONType JSONValue types -> JSONValue TArray
VString : (s : String) -> JSONValue TString
VNumber : (d : Double) -> JSONValue TNumber
VBool : (b : Bool) -> JSONValue TBool
VNull : JSONValue TNull
%name JSONValue value, value2, value3
Parsers
We are going to get mutually recursive here. Instead of using a mutual
block,
we will use the more modern style of declaring all our types ahead of our
definitions.
export
object : Parser (JSONValue TObject)
export
array : Parser (JSONValue TArray)
export
string : Parser (JSONValue TString)
export
number : Parser (JSONValue TNumber)
export
bool : Parser (JSONValue TBool)
export
null : Parser (JSONValue TNull)
Define a whitespace
character class based on the json specifications
whitespace : Parser Char
whitespace = do
result <-
parseChar (\x => any (== x) $ the (List _) [' ', '\n', '\r', '\t']) id
case result of
GotChar char => pure char
GotError err => throwParseError "Expected whitespace, got: \{show err}"
EndOfInput => throwParseError "End of Input"
Convenience function
dpairize : {t : JSONType} ->
Parser (JSONValue t) -> Parser (t' : JSONType ** JSONValue t')
dpairize x = do
x <- x
pure (_ ** x)
Top level json value parser
export
value : Parser (t : JSONType ** JSONValue t)
value = do
surround whitespace $ oneOfE
(throwParseError "Expected JSON Value")
(the (List _)
[
dpairize object
, dpairize array
, dpairize string
, dpairize number
, dpairize bool
, dpairize null
])
Now go through our json value types
object = do
oneOfE
(throwParseError "Expected Object")
(the (List _) [emptyObject, occupiedObject])
where
emptyObject : Parser (JSONValue TObject)
emptyObject = do
delimited '{' '}' (nom whitespace)
pure $ VObject Nil
keyValue : Parser (t : JSONType ** (String, JSONValue t))
keyValue = do
VString key <- surround whitespace string
_ <- parseExactChar ':'
(typ ** val) <- value
pure (typ ** (key, val))
restKeyValue : Parser (t : JSONType ** (String, JSONValue t))
restKeyValue = do
_ <- parseExactChar ','
keyValue
pairs : Parser (List1 (t : JSONType ** (String, JSONValue t)))
pairs = do
first <- keyValue
rest <- many restKeyValue
pure $ first ::: rest
occupiedObject : Parser (JSONValue TObject)
occupiedObject = do
val <- delimited '{' '}' pairs
let (types ** xs) = DList.fromList (forget val)
pure $ VObject xs
array = do
oneOfE
(throwParseError "Expected Array")
(the (List _) [emptyArray, occupiedArray])
where
emptyArray : Parser (JSONValue TArray)
emptyArray = do
_ <- parseExactChar '['
_ <- many whitespace
_ <- parseExactChar ']'
pure $ VArray Nil
restValue : Parser (t : JSONType ** JSONValue t)
restValue = do
_ <- parseExactChar ','
value
occupiedArray : Parser (JSONValue TArray)
occupiedArray = do
_ <- parseExactChar '['
first <- value
rest <- many restValue
_ <- parseExactChar ']'
let (types ** xs) = DList.fromList (first :: rest)
pure $ VArray xs
string = do
_ <- parseExactChar '"'
-- TODO: Handle control characters properly
e1 <- parseError "Expected non-quote, got quote"
e2 <- parseError "End of input"
contents <- parseString . many $ parseCharE (not . (== '"')) (\_ => e1) e2
_ <- parseExactChar '"'
pure $ VString contents
number = do
d <- double
pure $ VNumber d
bool = do
oneOfE
(throwParseError "Expected Bool")
(the (List _) [true, false])
where
true : Parser (JSONValue TBool)
true = do
_ <- exactString "true"
pure $ VBool True
false : Parser (JSONValue TBool)
false = do
_ <- exactString "false"
pure $ VBool False
null = do
_ <- exactString "null"
pure VNull
Unit tests
Quick smoke test
-- @@test JSON Quick Smoke
quickSmoke : IO Bool
quickSmoke = do
let input = "{\"string\":\"string\",\"number\":5,\"true\":true,\"false\":false,\"null\":null,\"array\":[1,2,3]}"
putStrLn input
Right parsed <- runFirstIO object input
| Left err => do
printLn err
pure False
putStrLn "Input: \{input}\nOutput: \{show parsed}"
case parsed of
VObject xs => ?quickSmoke_rhs_0
_ => pure False