advent/src/Parser/JSON.md

6.6 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
  pnote "Whitespace character"
  result <- 
    parseChar (\x => any (== x) $ the (List _) [' ', '\n', '\r', '\t'])
  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 
  pnote "JSON Value"
  surround whitespace $ oneOfE 
    "Expected JSON Value" 
    [
      dpairize object
    , dpairize array
    , dpairize string
    , dpairize number
    , dpairize bool
    , dpairize null
    ]

Now go through our json value types

object = do
  pnote "JSON Object"
  oneOfE 
    "Expected Object" 
    [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
  pnote "JSON Array"
  oneOfE 
    "Expected Array" 
    [emptyArray, occupiedArray]
  where
    emptyArray : Parser (JSONValue TArray)
    emptyArray = do
      delimited '[' ']' (nom whitespace)
      pure $ VArray Nil
    restValue : Parser (t : JSONType ** JSONValue t)
    restValue = do
      _ <- parseExactChar ','
      value
    values : Parser (List1 (t : JSONType ** JSONValue t))
    values = do
      first <- value
      rest <- many restValue
      pure $ first ::: rest 
    occupiedArray : Parser (JSONValue TArray)
    occupiedArray = do
      xs <- delimited '[' ']' values
      let (types ** xs) = DList.fromList (forget xs)
      pure $ VArray xs
string = do
  pnote "JSON String"
  str <- parseString $ delimited '"' '"' (many stringCharacter)
  pure $ VString str
  where
    -- TODO: Handle control characters properly
    stringCharacter : Parser Char
    stringCharacter = do
      result <- parseChar (not . (== '"'))
      case result of
        GotChar char => pure char
        GotError err =>
          throwParseError "Expected string character, got \{show err}"
        EndOfInput => throwParseError "Unexpected end of input"
number = do
  pnote "JSON Number"
  d <- double
  pure $ VNumber d
bool = do
  pnote "JSON Bool"
  oneOfE
    "Expected Bool"
    [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
  pnote "JSON null"
  _ <- 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 (type ** parsed) <- runFirstIODebug value input
    | Left err => do
      printLn err
      pure False
  putStrLn "Input: \{input}\nOutput: \{show type} -> \{show parsed}"
  let reference_object = 
    VObject [
      ("string", VString "string")
    , ("number", VNumber 5.0)
    , ("true", VBool True)
    , ("false", VBool False)
    , ("null", VNull)
    , ("array", VArray [
        VNumber 1.0
      , VNumber 2.0
      , VNumber 3.0
      ])
    ]
  case type of
    TObject => pure $ parsed == reference_object
    _ => pure False