Compare commits
15 commits
5a47d5548c
...
2111e20f33
Author | SHA1 | Date | |
---|---|---|---|
2111e20f33 | |||
e46e1663b9 | |||
69e36774b5 | |||
cc9b0edfb2 | |||
8b111a1839 | |||
1a9fce3860 | |||
fdb166a2a6 | |||
352822a518 | |||
7c7950956a | |||
c1a2cf4963 | |||
025b76672d | |||
6734740946 | |||
9515e3e1c5 | |||
55813e4464 | |||
5ced705083 |
2 changed files with 62 additions and 153 deletions
|
@ -20,11 +20,8 @@ Combine the parser state at time of error with an error message.
|
||||||
```idris
|
```idris
|
||||||
public export
|
public export
|
||||||
data ParseError : Type where
|
data ParseError : Type where
|
||||||
-- TODO: Rename this constructor
|
|
||||||
MkParseError : (state : ParserInternal Id) -> (message : String) -> ParseError
|
MkParseError : (state : ParserInternal Id) -> (message : String) -> ParseError
|
||||||
BeforeParse : (message : String) -> ParseError
|
BeforeParse : (message : String) -> ParseError
|
||||||
NestedErrors : (state : ParserInternal Id) -> (message : String)
|
|
||||||
-> (rest : List ParseError) -> ParseError
|
|
||||||
```
|
```
|
||||||
|
|
||||||
<!-- idris
|
<!-- idris
|
||||||
|
@ -37,13 +34,6 @@ Show ParseError where
|
||||||
in "Error at line \{line}, column \{col} (\{position}): \{message}"
|
in "Error at line \{line}, column \{col} (\{position}): \{message}"
|
||||||
show (BeforeParse message) =
|
show (BeforeParse message) =
|
||||||
"Error before parsing: \{message}"
|
"Error before parsing: \{message}"
|
||||||
show (NestedErrors state message rest) =
|
|
||||||
let rest = assert_total $joinBy "\n" . map ((" " ++) . show) $ rest
|
|
||||||
(line, col) = positionPair state
|
|
||||||
(line, col) = (show line, show col)
|
|
||||||
position = show state.position.index
|
|
||||||
first = "Error at line \{line}, column \{col} (\{position}): \{message}"
|
|
||||||
in "\{first}\n\{rest}"
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## Type Alias
|
## Type Alias
|
||||||
|
@ -51,7 +41,7 @@ Show ParseError where
|
||||||
```idris
|
```idris
|
||||||
public export
|
public export
|
||||||
Parser : Type -> Type
|
Parser : Type -> Type
|
||||||
Parser a = Eff [ParserState, Except ParseError] a
|
Parser a = Eff [ParserState, Except ParseError, Choose] a
|
||||||
```
|
```
|
||||||
|
|
||||||
## Error Generation
|
## Error Generation
|
||||||
|
@ -103,7 +93,7 @@ no paths in the `Choice` effect produce a returning parse.
|
||||||
export
|
export
|
||||||
rundownFirst : (f : Parser a) -> Eff [ParserState] (Either ParseError a)
|
rundownFirst : (f : Parser a) -> Eff [ParserState] (Either ParseError a)
|
||||||
rundownFirst f =
|
rundownFirst f =
|
||||||
runExcept f
|
runExcept . guardMaybe "No returning parses" . runChoose {f = Maybe} $ f
|
||||||
```
|
```
|
||||||
|
|
||||||
Provide wrappers for `rundownFirst` for evaluating it in various contexts.
|
Provide wrappers for `rundownFirst` for evaluating it in various contexts.
|
||||||
|
@ -158,29 +148,27 @@ tryMaybe f = try (map Just f) (\_ => pure Nothing)
|
||||||
export
|
export
|
||||||
tryEither : (f : Parser a) -> Parser (Either ParseError a)
|
tryEither : (f : Parser a) -> Parser (Either ParseError a)
|
||||||
tryEither f = try (map Right f) (pure . Left)
|
tryEither f = try (map Right f) (pure . Left)
|
||||||
|
|
||||||
|
||| Converts any errors thrown by `f` into silent backtracking within `Choose`
|
||||||
|
export
|
||||||
|
tryEmpty : (f : Parser a) -> Parser a
|
||||||
|
tryEmpty f = try f (\_ => empty)
|
||||||
```
|
```
|
||||||
|
|
||||||
Attempt to parse one of the given input parsers, in the provided order, invoking
|
Attempt to parse one of the given input parsers, in the provided order, invoking
|
||||||
the provided error action on failure.
|
the provided error action on failure. This will suppress any errors returned by
|
||||||
|
the input parsers by mapping them to `empty`.
|
||||||
|
|
||||||
The state will not be modified when an input parser fails
|
The state will not be modified when an input parser fails
|
||||||
|
|
||||||
```idris
|
```idris
|
||||||
export
|
export
|
||||||
oneOfE : (err : String) -> List (Parser a) -> Parser a
|
oneOfE : Foldable f =>
|
||||||
oneOfE err xs = do
|
(err : Parser a) -> f (Parser a) -> Parser a
|
||||||
start <- save
|
oneOfE err xs = foldr altE err xs
|
||||||
oneOfE' err start [] xs
|
|
||||||
where
|
where
|
||||||
oneOfE' : (err : String) -> (start : ParserInternal Id)
|
altE : Parser a -> Parser a -> Parser a
|
||||||
-> (errs : List ParseError) -> List (Parser a) -> Parser a
|
altE f rest = (tryEmpty f) `alt` rest
|
||||||
oneOfE' err start errs [] = do
|
|
||||||
throw $ NestedErrors start err (reverse errs)
|
|
||||||
oneOfE' err start errs (x :: xs) = do
|
|
||||||
x <- tryEither x
|
|
||||||
case x of
|
|
||||||
Right val => pure val
|
|
||||||
Left error => oneOfE' err start (error :: errs) xs
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Attempt to parse 0+ of an item
|
Attempt to parse 0+ of an item
|
||||||
|
@ -210,7 +198,6 @@ Lift a parser producing a `List` or `List1` of `Char` into a parser producing a
|
||||||
`String`
|
`String`
|
||||||
|
|
||||||
```idris
|
```idris
|
||||||
-- TODO: Rename these
|
|
||||||
export
|
export
|
||||||
parseString : Parser (List Char) -> Parser String
|
parseString : Parser (List Char) -> Parser String
|
||||||
parseString x = do
|
parseString x = do
|
||||||
|
@ -263,40 +250,6 @@ exactString str with (asList str)
|
||||||
pure input
|
pure input
|
||||||
```
|
```
|
||||||
|
|
||||||
Wrap a parser in delimiter characters, discarding the value of the delimiters
|
|
||||||
|
|
||||||
```idris
|
|
||||||
export
|
|
||||||
delimited : (before, after : Char) -> Parser a -> Parser a
|
|
||||||
delimited before after x = do
|
|
||||||
starting_state <- save
|
|
||||||
_ <- parseExactChar before
|
|
||||||
val <- x
|
|
||||||
Just _ <- tryMaybe $ parseExactChar after
|
|
||||||
| _ => throw $
|
|
||||||
MkParseError starting_state "Unmatched delimiter \{show before}"
|
|
||||||
pure val
|
|
||||||
```
|
|
||||||
|
|
||||||
Consume any number of characters of the provided character class and discard the
|
|
||||||
result. Also a version for doing so on both sides of a provided parser
|
|
||||||
|
|
||||||
```idris
|
|
||||||
export
|
|
||||||
nom : Parser Char -> Parser ()
|
|
||||||
nom x = do
|
|
||||||
_ <- many x
|
|
||||||
pure ()
|
|
||||||
|
|
||||||
export
|
|
||||||
surround : (around : Parser Char) -> (item : Parser a) -> Parser a
|
|
||||||
surround around item = do
|
|
||||||
nom around
|
|
||||||
val <- item
|
|
||||||
nom around
|
|
||||||
pure val
|
|
||||||
```
|
|
||||||
|
|
||||||
### Composition of boolean functions
|
### Composition of boolean functions
|
||||||
|
|
||||||
```idris
|
```idris
|
||||||
|
|
|
@ -61,24 +61,13 @@ Show (JSONValue t) where
|
||||||
in assert_total $ "{\{joinBy "," xs}}"
|
in assert_total $ "{\{joinBy "," xs}}"
|
||||||
show (VArray xs) =
|
show (VArray xs) =
|
||||||
let xs = dMap (\_,e => show e) xs
|
let xs = dMap (\_,e => show e) xs
|
||||||
in assert_total $ "[\{joinBy "," xs}]"
|
in assert_total $ "[\{joinBy "," . map show $ xs}]"
|
||||||
show (VString s) = "\"\{s}\""
|
show (VString s) = "\"\{s}\""
|
||||||
show (VNumber d) = show d
|
show (VNumber d) = show d
|
||||||
show (VBool False) = "false"
|
show (VBool False) = "false"
|
||||||
show (VBool True) = "true"
|
show (VBool True) = "true"
|
||||||
show VNull = "null"
|
show VNull = "null"
|
||||||
|
|
||||||
-- TODO: Deal with keys potentially having different orders in different objects
|
|
||||||
Eq (JSONValue t) where
|
|
||||||
(VObject xs) == (VObject ys) =
|
|
||||||
assert_total $ xs $== ys
|
|
||||||
(VArray xs) == (VArray ys) =
|
|
||||||
assert_total $ xs $== ys
|
|
||||||
(VString s) == (VString str) = s == str
|
|
||||||
(VNumber d) == (VNumber dbl) = d == dbl
|
|
||||||
(VBool b) == (VBool x) = b == x
|
|
||||||
VNull == VNull = True
|
|
||||||
|
|
||||||
%hide Language.Reflection.TT.WithFC.value
|
%hide Language.Reflection.TT.WithFC.value
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
@ -132,8 +121,10 @@ Top level json value parser
|
||||||
export
|
export
|
||||||
value : Parser (t : JSONType ** JSONValue t)
|
value : Parser (t : JSONType ** JSONValue t)
|
||||||
value = do
|
value = do
|
||||||
surround whitespace $ oneOfE
|
_ <- many whitespace
|
||||||
"Expected JSON Value"
|
val <- oneOfE
|
||||||
|
(throwParseError "Expected JSON Value")
|
||||||
|
(the (List _)
|
||||||
[
|
[
|
||||||
dpairize object
|
dpairize object
|
||||||
, dpairize array
|
, dpairize array
|
||||||
|
@ -141,7 +132,9 @@ value = do
|
||||||
, dpairize number
|
, dpairize number
|
||||||
, dpairize bool
|
, dpairize bool
|
||||||
, dpairize null
|
, dpairize null
|
||||||
]
|
])
|
||||||
|
_ <- many whitespace
|
||||||
|
pure val
|
||||||
```
|
```
|
||||||
|
|
||||||
Now go through our json value types
|
Now go through our json value types
|
||||||
|
@ -149,76 +142,72 @@ Now go through our json value types
|
||||||
```idris
|
```idris
|
||||||
object = do
|
object = do
|
||||||
oneOfE
|
oneOfE
|
||||||
"Expected Object"
|
(throwParseError "Expected Object")
|
||||||
[emptyObject, occupiedObject]
|
(the (List _) [emptyObject, occupiedObject])
|
||||||
where
|
where
|
||||||
emptyObject : Parser (JSONValue TObject)
|
emptyObject : Parser (JSONValue TObject)
|
||||||
emptyObject = do
|
emptyObject = do
|
||||||
delimited '{' '}' (nom whitespace)
|
_ <- parseExactChar '{'
|
||||||
|
_ <- many whitespace
|
||||||
|
_ <- parseExactChar '}'
|
||||||
pure $ VObject Nil
|
pure $ VObject Nil
|
||||||
keyValue : Parser (t : JSONType ** (String, JSONValue t))
|
firstKeyValue : Parser (t : JSONType ** (String, JSONValue t))
|
||||||
keyValue = do
|
firstKeyValue = do
|
||||||
VString key <- surround whitespace string
|
_ <- many whitespace
|
||||||
|
VString key <- string
|
||||||
|
_ <- many whitespace
|
||||||
_ <- parseExactChar ':'
|
_ <- parseExactChar ':'
|
||||||
(typ ** val) <- value
|
(typ ** val) <- value
|
||||||
pure (typ ** (key, val))
|
pure (typ ** (key, val))
|
||||||
restKeyValue : Parser (t : JSONType ** (String, JSONValue t))
|
restKeyValue : Parser (t : JSONType ** (String, JSONValue t))
|
||||||
restKeyValue = do
|
restKeyValue = do
|
||||||
_ <- parseExactChar ','
|
_ <- parseExactChar ','
|
||||||
keyValue
|
firstKeyValue
|
||||||
pairs : Parser (List1 (t : JSONType ** (String, JSONValue t)))
|
|
||||||
pairs = do
|
|
||||||
first <- keyValue
|
|
||||||
rest <- many restKeyValue
|
|
||||||
pure $ first ::: rest
|
|
||||||
occupiedObject : Parser (JSONValue TObject)
|
occupiedObject : Parser (JSONValue TObject)
|
||||||
occupiedObject = do
|
occupiedObject = do
|
||||||
val <- delimited '{' '}' pairs
|
_ <- parseExactChar '{'
|
||||||
let (types ** xs) = DList.fromList (forget val)
|
first <- firstKeyValue
|
||||||
|
rest <- many restKeyValue
|
||||||
|
_ <- parseExactChar '}'
|
||||||
|
let (types ** xs) = DList.fromList (first :: rest)
|
||||||
pure $ VObject xs
|
pure $ VObject xs
|
||||||
```
|
```
|
||||||
|
|
||||||
```idris
|
```idris
|
||||||
array = do
|
array = do
|
||||||
oneOfE
|
oneOfE
|
||||||
"Expected Array"
|
(throwParseError "Expected Array")
|
||||||
[emptyArray, occupiedArray]
|
(the (List _) [emptyArray, occupiedArray])
|
||||||
where
|
where
|
||||||
emptyArray : Parser (JSONValue TArray)
|
emptyArray : Parser (JSONValue TArray)
|
||||||
emptyArray = do
|
emptyArray = do
|
||||||
delimited '[' ']' (nom whitespace)
|
_ <- parseExactChar '['
|
||||||
|
_ <- many whitespace
|
||||||
|
_ <- parseExactChar ']'
|
||||||
pure $ VArray Nil
|
pure $ VArray Nil
|
||||||
restValue : Parser (t : JSONType ** JSONValue t)
|
restValue : Parser (t : JSONType ** JSONValue t)
|
||||||
restValue = do
|
restValue = do
|
||||||
_ <- parseExactChar ','
|
_ <- parseExactChar ','
|
||||||
value
|
value
|
||||||
values : Parser (List1 (t : JSONType ** JSONValue t))
|
|
||||||
values = do
|
|
||||||
first <- value
|
|
||||||
rest <- many restValue
|
|
||||||
pure $ first ::: rest
|
|
||||||
occupiedArray : Parser (JSONValue TArray)
|
occupiedArray : Parser (JSONValue TArray)
|
||||||
occupiedArray = do
|
occupiedArray = do
|
||||||
_ <- parseExactChar '['
|
_ <- parseExactChar '['
|
||||||
xs <- values
|
first <- value
|
||||||
|
rest <- many restValue
|
||||||
_ <- parseExactChar ']'
|
_ <- parseExactChar ']'
|
||||||
-- TODO: Why is this busted?
|
let (types ** xs) = DList.fromList (first :: rest)
|
||||||
-- xs <- delimited '[' ']' values
|
|
||||||
let (types ** xs) = DList.fromList (forget xs)
|
|
||||||
pure $ VArray xs
|
pure $ VArray xs
|
||||||
```
|
```
|
||||||
|
|
||||||
```idris
|
```idris
|
||||||
string = do
|
string = do
|
||||||
str <- parseString $ delimited '"' '"' (many stringCharacter)
|
_ <- parseExactChar '"'
|
||||||
pure $ VString str
|
|
||||||
where
|
|
||||||
-- TODO: Handle control characters properly
|
-- TODO: Handle control characters properly
|
||||||
stringCharacter : Parser Char
|
|
||||||
stringCharacter = do
|
|
||||||
e1 <- parseError "Expected non-quote, got quote"
|
e1 <- parseError "Expected non-quote, got quote"
|
||||||
e2 <- parseError "End of input"
|
e2 <- parseError "End of input"
|
||||||
parseCharE (not . (== '"')) (\_ => e1) e2
|
contents <- parseString . many $ parseCharE (not . (== '"')) (\_ => e1) e2
|
||||||
|
_ <- parseExactChar '"'
|
||||||
|
pure $ VString contents
|
||||||
```
|
```
|
||||||
|
|
||||||
```idris
|
```idris
|
||||||
|
@ -230,8 +219,8 @@ number = do
|
||||||
```idris
|
```idris
|
||||||
bool = do
|
bool = do
|
||||||
oneOfE
|
oneOfE
|
||||||
"Expected Bool"
|
(throwParseError "Expected Bool")
|
||||||
[true, false]
|
(the (List _) [true, false])
|
||||||
where
|
where
|
||||||
true : Parser (JSONValue TBool)
|
true : Parser (JSONValue TBool)
|
||||||
true = do
|
true = do
|
||||||
|
@ -248,36 +237,3 @@ null = do
|
||||||
_ <- exactString "null"
|
_ <- exactString "null"
|
||||||
pure VNull
|
pure VNull
|
||||||
```
|
```
|
||||||
|
|
||||||
## Unit tests
|
|
||||||
|
|
||||||
Quick smoke test
|
|
||||||
|
|
||||||
```idris
|
|
||||||
-- @@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) <- runFirstIO 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
|
|
||||||
```
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue