Compare commits
16 commits
e3d563e6a3
...
2111e20f33
Author | SHA1 | Date | |
---|---|---|---|
2111e20f33 | |||
e46e1663b9 | |||
69e36774b5 | |||
cc9b0edfb2 | |||
8b111a1839 | |||
1a9fce3860 | |||
fdb166a2a6 | |||
352822a518 | |||
7c7950956a | |||
c1a2cf4963 | |||
025b76672d | |||
6734740946 | |||
9515e3e1c5 | |||
55813e4464 | |||
5ced705083 | |||
3029432699 |
5 changed files with 508 additions and 0 deletions
|
@ -68,6 +68,14 @@ solution.
|
|||
|
||||
Internal state of a parser
|
||||
|
||||
- [Numbers](src/Parser/Numbers.md)
|
||||
|
||||
Parsers for numerical values in multiple bases
|
||||
|
||||
- [JSON](src/Parser/JSON.md)
|
||||
|
||||
JSON Parser
|
||||
|
||||
## Index of years and days
|
||||
|
||||
- 2015
|
||||
|
|
|
@ -19,6 +19,7 @@ depends = base
|
|||
, tailrec
|
||||
, eff
|
||||
, elab-util
|
||||
, sop
|
||||
, ansi
|
||||
, if-unsolved-implicit
|
||||
, c-ffi
|
||||
|
@ -32,6 +33,8 @@ modules = Runner
|
|||
, Array
|
||||
, Parser
|
||||
, Parser.Interface
|
||||
, Parser.Numbers
|
||||
, Parser.JSON
|
||||
|
||||
-- main file (i.e. file to load at REPL)
|
||||
main = Main
|
||||
|
|
|
@ -238,6 +238,7 @@ parseTheseChars cs = do
|
|||
Attempt to parse an exact string
|
||||
|
||||
```idris
|
||||
export
|
||||
exactString : String -> Parser String
|
||||
exactString str with (asList str)
|
||||
exactString "" | [] = pure ""
|
||||
|
|
239
src/Parser/JSON.md
Normal file
239
src/Parser/JSON.md
Normal file
|
@ -0,0 +1,239 @@
|
|||
# JSON Parser
|
||||
|
||||
```idris
|
||||
module Parser.JSON
|
||||
|
||||
import public Parser
|
||||
import public Parser.Numbers
|
||||
|
||||
import Structures.Dependent.DList
|
||||
```
|
||||
|
||||
<!-- idris
|
||||
import System
|
||||
import Derive.Prelude
|
||||
import Generics.Derive
|
||||
|
||||
%hide Generics.Derive.Eq
|
||||
%hide Generics.Derive.Ord
|
||||
%hide Generics.Derive.Show
|
||||
|
||||
%language ElabReflection
|
||||
-->
|
||||
|
||||
## JSON components
|
||||
|
||||
Types a JSON value is allowed to have
|
||||
|
||||
```idris
|
||||
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
|
||||
|
||||
```idris
|
||||
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
|
||||
```
|
||||
|
||||
<!-- idris
|
||||
Show (JSONValue t) where
|
||||
show (VObject xs) =
|
||||
let xs = dMap (\_,(key, value) => "\"\{key}\":\{show value}") xs
|
||||
in assert_total $ "{\{joinBy "," xs}}"
|
||||
show (VArray xs) =
|
||||
let xs = dMap (\_,e => show e) xs
|
||||
in assert_total $ "[\{joinBy "," . map show $ xs}]"
|
||||
show (VString s) = "\"\{s}\""
|
||||
show (VNumber d) = show d
|
||||
show (VBool False) = "false"
|
||||
show (VBool True) = "true"
|
||||
show VNull = "null"
|
||||
|
||||
%hide Language.Reflection.TT.WithFC.value
|
||||
-->
|
||||
|
||||
## 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.
|
||||
|
||||
```idris
|
||||
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
|
||||
|
||||
```idris
|
||||
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
|
||||
|
||||
```idris
|
||||
dpairize : {t : JSONType} ->
|
||||
Parser (JSONValue t) -> Parser (t' : JSONType ** JSONValue t')
|
||||
dpairize x = do
|
||||
x <- x
|
||||
pure (_ ** x)
|
||||
```
|
||||
|
||||
Top level json value parser
|
||||
|
||||
```idris
|
||||
export
|
||||
value : Parser (t : JSONType ** JSONValue t)
|
||||
value = do
|
||||
_ <- many whitespace
|
||||
val <- oneOfE
|
||||
(throwParseError "Expected JSON Value")
|
||||
(the (List _)
|
||||
[
|
||||
dpairize object
|
||||
, dpairize array
|
||||
, dpairize string
|
||||
, dpairize number
|
||||
, dpairize bool
|
||||
, dpairize null
|
||||
])
|
||||
_ <- many whitespace
|
||||
pure val
|
||||
```
|
||||
|
||||
Now go through our json value types
|
||||
|
||||
```idris
|
||||
object = do
|
||||
oneOfE
|
||||
(throwParseError "Expected Object")
|
||||
(the (List _) [emptyObject, occupiedObject])
|
||||
where
|
||||
emptyObject : Parser (JSONValue TObject)
|
||||
emptyObject = do
|
||||
_ <- parseExactChar '{'
|
||||
_ <- many whitespace
|
||||
_ <- parseExactChar '}'
|
||||
pure $ VObject Nil
|
||||
firstKeyValue : Parser (t : JSONType ** (String, JSONValue t))
|
||||
firstKeyValue = do
|
||||
_ <- many whitespace
|
||||
VString key <- string
|
||||
_ <- many whitespace
|
||||
_ <- parseExactChar ':'
|
||||
(typ ** val) <- value
|
||||
pure (typ ** (key, val))
|
||||
restKeyValue : Parser (t : JSONType ** (String, JSONValue t))
|
||||
restKeyValue = do
|
||||
_ <- parseExactChar ','
|
||||
firstKeyValue
|
||||
occupiedObject : Parser (JSONValue TObject)
|
||||
occupiedObject = do
|
||||
_ <- parseExactChar '{'
|
||||
first <- firstKeyValue
|
||||
rest <- many restKeyValue
|
||||
_ <- parseExactChar '}'
|
||||
let (types ** xs) = DList.fromList (first :: rest)
|
||||
pure $ VObject xs
|
||||
```
|
||||
|
||||
```idris
|
||||
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
|
||||
```
|
||||
|
||||
```idris
|
||||
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
|
||||
```
|
||||
|
||||
```idris
|
||||
number = do
|
||||
d <- double
|
||||
pure $ VNumber d
|
||||
```
|
||||
|
||||
```idris
|
||||
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
|
||||
```
|
||||
|
||||
```idris
|
||||
null = do
|
||||
_ <- exactString "null"
|
||||
pure VNull
|
||||
```
|
257
src/Parser/Numbers.md
Normal file
257
src/Parser/Numbers.md
Normal file
|
@ -0,0 +1,257 @@
|
|||
# Numerical Parsers
|
||||
|
||||
```idris
|
||||
module Parser.Numbers
|
||||
|
||||
import public Parser
|
||||
|
||||
import Data.Vect
|
||||
import Control.Eff
|
||||
```
|
||||
|
||||
<!-- idris
|
||||
import System
|
||||
-->
|
||||
|
||||
## Base Abstraction
|
||||
|
||||
```idris
|
||||
public export
|
||||
record Base where
|
||||
constructor MkBase
|
||||
base : Nat
|
||||
digits : Vect base Char
|
||||
%name Base b
|
||||
|
||||
export
|
||||
hasDigit : Base -> Char -> Bool
|
||||
hasDigit (MkBase base digits) c = any (== c) digits
|
||||
|
||||
export
|
||||
digitValue : Base -> Char -> Maybe Nat
|
||||
digitValue (MkBase base digits) c = digitValue' digits 0
|
||||
where
|
||||
digitValue' : Vect n Char -> (idx : Nat) -> Maybe Nat
|
||||
digitValue' [] idx = Nothing
|
||||
digitValue' (x :: xs) idx =
|
||||
if x == c
|
||||
then Just idx
|
||||
else digitValue' xs (S idx)
|
||||
|
||||
public export
|
||||
base10 : Base
|
||||
base10 = MkBase 10
|
||||
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
|
||||
|
||||
public export
|
||||
hex : Base
|
||||
hex = MkBase 16
|
||||
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
|
||||
```
|
||||
|
||||
## Parsers
|
||||
|
||||
### Nat
|
||||
|
||||
```idris
|
||||
export
|
||||
nat : Base -> Parser Nat
|
||||
nat b = do
|
||||
error <- replaceError "Expected digit"
|
||||
(first ::: rest) <- atLeastOne error parseDigit
|
||||
pure $ foldl (\acc, e => 10 * acc + e) first rest
|
||||
where
|
||||
parseDigit : Parser Nat
|
||||
parseDigit = do
|
||||
GotChar char <- parseChar (hasDigit b) id
|
||||
| GotError e => throwParseError "\{show e} is not a digit"
|
||||
| EndOfInput => throwParseError "End Of Input"
|
||||
case digitValue b char of
|
||||
Nothing =>
|
||||
throwParseError "Failed to parse as base \{show b.base}: \{show char}"
|
||||
Just x => pure x
|
||||
|
||||
export
|
||||
natBase10 : Parser Nat
|
||||
natBase10 = nat base10
|
||||
```
|
||||
|
||||
### Integer
|
||||
|
||||
```idris
|
||||
export
|
||||
integer : Base -> Parser Integer
|
||||
integer b = do
|
||||
negative <- map isJust . tryMaybe $ parseExactChar '-'
|
||||
value <- map natToInteger $ nat b
|
||||
if negative
|
||||
then pure $ negate value
|
||||
else pure $ value
|
||||
|
||||
export
|
||||
integerBase10 : Parser Integer
|
||||
integerBase10 = integer base10
|
||||
```
|
||||
|
||||
### Double
|
||||
|
||||
```idris
|
||||
-- TODO: Replicate `parseDouble` logic and make this base-generic
|
||||
export
|
||||
double : Parser Double
|
||||
double = do
|
||||
starting_state <- save
|
||||
integer <- integer
|
||||
fraction <- tryMaybe fraction
|
||||
exponent <- tryMaybe exponent
|
||||
let str = case (fraction, exponent) of
|
||||
(Nothing, Nothing) =>
|
||||
integer
|
||||
(Nothing, (Just exponent)) =>
|
||||
"\{integer}e\{exponent}"
|
||||
((Just fraction), Nothing) =>
|
||||
"\{integer}.\{fraction}"
|
||||
((Just fraction), (Just exponent)) =>
|
||||
"\{integer}.\{fraction}e\{exponent}"
|
||||
Just out <- pure $ parseDouble str
|
||||
| _ =>
|
||||
throw $ MkParseError starting_state "Std failed to parse as double: \{str}"
|
||||
pure out
|
||||
where
|
||||
parseDigit : Parser Char
|
||||
parseDigit = do
|
||||
GotChar char <- parseChar (hasDigit base10) id
|
||||
| GotError e => throwParseError "\{show e} is not a digit"
|
||||
| EndOfInput => throwParseError "End Of Input"
|
||||
pure char
|
||||
integer : Parser String
|
||||
integer = do
|
||||
sign <- tryMaybe $ parseExactChar '-'
|
||||
error <- replaceError "Expected digit"
|
||||
digits <- map forget $ atLeastOne error parseDigit
|
||||
case sign of
|
||||
Nothing => pure $ pack digits
|
||||
Just x => pure $ pack (x :: digits)
|
||||
fraction : Parser String
|
||||
fraction = do
|
||||
decimal <- parseExactChar '.'
|
||||
error <- replaceError "Expected digit"
|
||||
digits <- map forget $ atLeastOne error parseDigit
|
||||
pure $ pack digits
|
||||
exponent : Parser String
|
||||
exponent = do
|
||||
e <- parseTheseChars ['e', 'E']
|
||||
sign <- parseTheseChars ['+', '-']
|
||||
error <- replaceError "Expected digit"
|
||||
digits <- map forget $ atLeastOne error parseDigit
|
||||
pure . pack $ sign :: digits
|
||||
```
|
||||
|
||||
## Unit tests
|
||||
|
||||
Test roundtripping a value through the provided parser
|
||||
|
||||
```idris
|
||||
roundtrip : Eq a => Show a => a -> (p : Parser a) -> IO Bool
|
||||
roundtrip x p = do
|
||||
let string = show x
|
||||
putStrLn "Roundtripping \{string}"
|
||||
Just state <- newInternalIO string
|
||||
| _ => do
|
||||
putStrLn "Failed to produce parser for \{string}"
|
||||
pure False
|
||||
Right result <- runEff (rundownFirst p) [handleParserStateIO state] {m = IO}
|
||||
| Left err => do
|
||||
printLn err
|
||||
pure False
|
||||
putStrLn "Input: \{string} Output: \{show result}"
|
||||
pure $ x == result
|
||||
```
|
||||
|
||||
Do some roundtrip tests with the nat parser
|
||||
|
||||
```idris
|
||||
-- @@test Nat round trip
|
||||
natRoundTrip : IO Bool
|
||||
natRoundTrip = pure $
|
||||
!(roundtrip 0 natBase10)
|
||||
&& !(roundtrip 1 natBase10)
|
||||
&& !(roundtrip 100 natBase10)
|
||||
&& !(roundtrip 1234 natBase10)
|
||||
&& !(roundtrip 1234567890 natBase10)
|
||||
&& !(roundtrip 1234567890000 natBase10)
|
||||
&& !(roundtrip 12345678901234567890 natBase10)
|
||||
```
|
||||
|
||||
```idris
|
||||
-- @@test Integer round trip
|
||||
integerRoundTrip : IO Bool
|
||||
integerRoundTrip = pure $
|
||||
!(roundtrip 0 integerBase10)
|
||||
&& !(roundtrip 1 integerBase10)
|
||||
&& !(roundtrip 100 integerBase10)
|
||||
&& !(roundtrip 1234 integerBase10)
|
||||
&& !(roundtrip 1234567890 integerBase10)
|
||||
&& !(roundtrip 1234567890000 integerBase10)
|
||||
&& !(roundtrip 12345678901234567890 integerBase10)
|
||||
&& !(roundtrip (-1) integerBase10)
|
||||
&& !(roundtrip (-100) integerBase10)
|
||||
&& !(roundtrip (-1234) integerBase10)
|
||||
&& !(roundtrip (-1234567890) integerBase10)
|
||||
&& !(roundtrip (-1234567890000) integerBase10)
|
||||
&& !(roundtrip (-12345678901234567890) integerBase10)
|
||||
```
|
||||
|
||||
Compare our parsing of a double to the standard library's
|
||||
|
||||
```idris
|
||||
compareDouble : String -> IO Bool
|
||||
compareDouble string = do
|
||||
Just state <- newInternalIO string
|
||||
| _ => do
|
||||
putStrLn "Failed to produce parser for \{string}"
|
||||
pure False
|
||||
Right result <-
|
||||
runEff (rundownFirst double) [handleParserStateIO state] {m = IO}
|
||||
| Left err => do
|
||||
printLn err
|
||||
pure False
|
||||
putStrLn "Input: \{string} Output: \{show result}"
|
||||
Just double' <- pure $ parseDouble string
|
||||
| _ => do
|
||||
printLn "Std failed to parse as double: \{string}"
|
||||
pure False
|
||||
pure $ result == double'
|
||||
```
|
||||
|
||||
```idris
|
||||
-- @@test Double Std Comparison
|
||||
doubleRoundTrip : IO Bool
|
||||
doubleRoundTrip = pure $
|
||||
!(compareDouble "0")
|
||||
&& !(compareDouble "1")
|
||||
&& !(compareDouble "100")
|
||||
&& !(compareDouble "1234")
|
||||
&& !(compareDouble "1234567890")
|
||||
&& !(compareDouble "1234567890000")
|
||||
&& !(compareDouble "12345678901234567890")
|
||||
&& !(compareDouble "-1")
|
||||
&& !(compareDouble "-100")
|
||||
&& !(compareDouble "-1234")
|
||||
&& !(compareDouble "-1234567890")
|
||||
&& !(compareDouble "-1234567890000")
|
||||
&& !(compareDouble "-12345678901234567890")
|
||||
&& !(compareDouble "0.0")
|
||||
&& !(compareDouble "1.0")
|
||||
&& !(compareDouble "-1.0")
|
||||
&& !(compareDouble "-0.0")
|
||||
&& !(compareDouble "-0.0")
|
||||
&& !(compareDouble "0.1234")
|
||||
&& !(compareDouble "0.01234")
|
||||
&& !(compareDouble "-0.1234")
|
||||
&& !(compareDouble "-0.01234")
|
||||
&& !(compareDouble "1.234e+5")
|
||||
&& !(compareDouble "1.234e-5")
|
||||
&& !(compareDouble "-1.234e+5")
|
||||
&& !(compareDouble "-1.234e-5")
|
||||
```
|
Loading…
Add table
Reference in a new issue