diff --git a/json/spago.yaml b/json/spago.yaml new file mode 100644 index 00000000..50728146 --- /dev/null +++ b/json/spago.yaml @@ -0,0 +1,18 @@ +package: + name: json + dependencies: + - prelude + - arrays + - functions + - integers + - maybe + - either + - tuples + - foldable-traversable + - gen + - strings + - unfoldable + test: + main: Test.JSON.Main + dependencies: + - assert diff --git a/json/src/JSON.purs b/json/src/JSON.purs index c911b6e9..7d44e6c6 100644 --- a/json/src/JSON.purs +++ b/json/src/JSON.purs @@ -30,7 +30,7 @@ import Data.Function.Uncurried (runFn2, runFn3, runFn7) import Data.Int as Int import Data.Maybe (Maybe(..)) import JSON.Internal (JArray, JObject, JSON) -import JSON.Internal (JArray, JObject, JSON) as Exports +import JSON.Internal (JArray, JObject, JSON, isNull) as Exports import JSON.Internal as Internal -- | Attempts to parse a string as a JSON value. If parsing fails, an error message detailing the diff --git a/json/src/JSON.ss b/json/src/JSON.ss new file mode 100644 index 00000000..f1740fcb --- /dev/null +++ b/json/src/JSON.ss @@ -0,0 +1,31 @@ +(library (JSON foreign) + (export _null + fromBoolean + fromInt + fromString + fromJArray + fromJObject + print + printIndented) + (import (chezscheme) + (only (JSON.Internal foreign) json-stringify)) + + (define (coerce x) x) + + (define _null 'null) + + (define fromBoolean coerce) + + (define fromInt inexact) + + (define fromString coerce) + + (define fromJArray coerce) + + (define fromJObject coerce) + + (define print json-stringify) + + (define printIndented json-stringify) + + ) diff --git a/json/src/JSON/Array.ss b/json/src/JSON/Array.ss new file mode 100644 index 00000000..57801797 --- /dev/null +++ b/json/src/JSON/Array.ss @@ -0,0 +1,9 @@ +(library (JSON.Array foreign) + (export singleton) + (import (chezscheme) + (prefix (purs runtime srfi :214) srfi:214:)) + + (define (singleton x) (srfi:214:flexvector x)) + + ) + diff --git a/json/src/JSON/Internal.js b/json/src/JSON/Internal.js index 8c4e1617..32c0c419 100644 --- a/json/src/JSON/Internal.js +++ b/json/src/JSON/Internal.js @@ -57,3 +57,5 @@ export const _index = (nothing, just, ix, arr) => ix >= 0 && ix < arr.length ? just(arr[ix]) : nothing; export const _append = (xs, ys) => xs.concat(ys); + +export const isNull = (json) => json == null; diff --git a/json/src/JSON/Internal.purs b/json/src/JSON/Internal.purs index 503cfbc8..0eb7bbaa 100644 --- a/json/src/JSON/Internal.purs +++ b/json/src/JSON/Internal.purs @@ -140,3 +140,5 @@ foreign import _append JArray JArray JArray + +foreign import isNull :: JSON -> Boolean diff --git a/json/src/JSON/Internal.ss b/json/src/JSON/Internal.ss new file mode 100644 index 00000000..0d03a5d0 --- /dev/null +++ b/json/src/JSON/Internal.ss @@ -0,0 +1,384 @@ +(library (JSON.Internal foreign) + (export _parse + _fromNumberWithDefault + _case + toArray + fromArray + _fromEntries + _insert + _delete + _entries + _lookup + empty + length + _index + _append + isNull + + ; Exported for testing + json-parse + json-stringify) + (import (except (chezscheme) length) + (prefix (purs runtime srfi :214) srfi:214:) + (purs runtime pstring)) + + (define (_parse left right s) + (call/cc + (lambda (k) + (with-exception-handler + (lambda (e) (left (k (string->pstring (condition-message e))))) + (lambda () (right (json-parse s))))))) + + (define (_fromNumberWithDefault fallback n) + (if (or (nan? n) (not (finite? n))) + fallback + n)) + + (define (_case isNull isBool isNum isStr isArr isObj j) + (cond + [(pstring? j) (isStr j)] + [(eq? 'null j) (isNull j)] + [(boolean? j) (isBool j)] + [(number? j) (isNum (inexact j))] + [(srfi:214:flexvector? j) (isArr j)] + [(list? j) (isObj j)] + [else (error #f "Value is not JSON")])) + + (define (toArray v) v) + + (define (fromArray v) v) + + (define (_fromEntries fst snd entries) + (srfi:214:flexvector-fold (lambda (tail entry) + (cons (cons (pstring->symbol (fst entry)) (snd entry)) tail)) + '() + entries)) + + (define (_insert k v obj) + (cons (cons k v) (_delete k obj))) + + (define (_delete k obj) + (if (null? obj) + obj + (if (eq? (caar obj) k) + (cdr obj) + (cons (car obj) (_delete k (cdr obj)))))) + + (define (_entries tuple obj) + (srfi:214:list->flexvector (map (lambda (entry) + ((tuple (symbol->pstring (car entry))) (cdr entry))) + obj))) + + (define (_lookup nothing just key obj) + (let ([res (assq (pstring->symbol key) obj)]) + (if (not res) + nothing + (just (cdr res))))) + + (define empty (srfi:214:flexvector)) + + (define length srfi:214:flexvector-length) + + (define (_index nothing just ix arr) + (if (and (fx>=? ix 0) (fxpstring "true")) + (define false (string->pstring "false")) + (define null (string->pstring "null")) + + (define (expect-char cur expected) + (let ([ch (pstring-cursor-read-char cur)]) + (when (not (eqv? ch expected)) + (error #f (format "Unexpected '~a', was expecting '~a'" ch expected))))) + + (define (json-parse str) + (let* ([cur (pstring->cursor str)] + [res (read-json-value cur)]) + ; Do not allow trailing tokens after we've succesfully parsed a json value + (skip-whitespace cur) + (let ([ch (pstring-cursor-peek-char cur)]) + (if (not (eof-object? ch)) + (error #f (format "Unexpected token '~a'" ch)) + res)))) + + ; The basic expression parser + (define (read-json-value cur) + (skip-whitespace cur) + (let ([ch (pstring-cursor-peek-char cur)]) + (cond + [(eqv? ch #\t) + (begin + (pstring-cursor-read-char cur) + (expect-char cur #\r) + (expect-char cur #\u) + (expect-char cur #\e) + #t)] + [(eqv? ch #\f) + (begin + (pstring-cursor-read-char cur) + (expect-char cur #\a) + (expect-char cur #\l) + (expect-char cur #\s) + (expect-char cur #\e) + #f)] + [(eqv? ch #\n) + (begin + (pstring-cursor-read-char cur) + (expect-char cur #\u) + (expect-char cur #\l) + (expect-char cur #\l) + 'null)] + [(eqv? ch #\") (read-json-string cur)] + [(eqv? ch #\[) (read-json-array cur)] + [(eqv? ch #\{) (read-json-object cur)] + [(eqv? ch #\-) (begin + (pstring-cursor-read-char cur) + (* -1 (read-json-number cur)))] + [(char<=? #\0 ch #\9) (read-json-number cur)] + [else (error #f (format "Unexpected token '~a'" ch))]))) + + (define (read-unicode-escape cur) + (define (read-hex-digit cur) + (let ([ch (pstring-cursor-read-char cur)]) + (cond + [(eqv? ch #\0) 0] + [(eqv? ch #\1) 1] + [(eqv? ch #\2) 2] + [(eqv? ch #\3) 3] + [(eqv? ch #\4) 4] + [(eqv? ch #\5) 5] + [(eqv? ch #\6) 6] + [(eqv? ch #\7) 7] + [(eqv? ch #\8) 8] + [(eqv? ch #\9) 9] + [(or (eqv? ch #\A) (eqv? ch #\a)) 10] + [(or (eqv? ch #\B) (eqv? ch #\b)) 11] + [(or (eqv? ch #\C) (eqv? ch #\c)) 12] + [(or (eqv? ch #\D) (eqv? ch #\d)) 13] + [(or (eqv? ch #\E) (eqv? ch #\e)) 14] + [(or (eqv? ch #\F) (eqv? ch #\f)) 15] + [(eof-object? ch) (error #f "Unexpected end of input, was expecting a hex digit")] + [else (error #f (format "Unexpected token '~a', was expecting a hex digit" ch))]))) + + (let* ([first (read-hex-digit cur)] + [second (read-hex-digit cur)] + [third (read-hex-digit cur)] + [fourth (read-hex-digit cur)]) + (code-points->pstring + (+ (* 4096 first) + (* 256 second) + (* 16 third) + fourth)))) + + (define (read-escape cur) + (let ([ch (pstring-cursor-read-char cur)]) + (cond + [(eqv? ch #\b) (pstring #\backspace)] + [(eqv? ch #\f) (pstring #\page)] + [(eqv? ch #\r) (pstring #\return)] + [(eqv? ch #\n) (pstring #\newline)] + [(eqv? ch #\t) (pstring #\tab)] + [(eqv? ch #\/) (pstring #\/)] + [(eqv? ch #\u) (read-unicode-escape cur)] + [else (error #f (format "Invalid string escape ~a" ch))]))) + + (define (control-char? ch) + (fx<= (char->integer ch) #x1f)) + + (define (read-json-string cur) + (expect-char cur #\") + (let loop ([start (cursor->pstring cur)] [i 0] [parts '()]) + (let ([ch (pstring-cursor-read-char cur)]) + (cond + [(eqv? ch #\") (apply pstring-concat (reverse (cons (pstring-take start i) parts)))] + [(control-char? ch) (error #f "Invalid control character in string")] + [(eqv? ch #\\) + (let ([e (read-escape cur)]) + (loop (cursor->pstring cur) + 0 + (cons e (cons (pstring-take start i) parts))))] + [else (loop start (fx1+ i) parts)])))) + + (define (read-digit cur) + (let ([ch (pstring-cursor-read-char cur)]) + (cond + [(eqv? ch #\0) 0] + [(eqv? ch #\1) 1] + [(eqv? ch #\2) 2] + [(eqv? ch #\3) 3] + [(eqv? ch #\4) 4] + [(eqv? ch #\5) 5] + [(eqv? ch #\6) 6] + [(eqv? ch #\7) 7] + [(eqv? ch #\8) 8] + [(eqv? ch #\9) 9]))) + + (define (read-int cur) + (let loop ([n 0]) + (let ([ch (pstring-cursor-peek-char cur)]) + (cond + [(eof-object? ch) n] + [(char<=? #\0 ch #\9) (loop (+ (* n 10) (read-digit cur)))] + [else n])))) + + (define (read-number-fraction cur) + (let ([ch (pstring-cursor-peek-char cur)]) + (if (eqv? ch #\.) + (begin + (pstring-cursor-read-char cur) + (let loop ([n 0] [len 0]) + (let ([ch (pstring-cursor-peek-char cur)]) + (cond + [(and (char? ch) (char<=? #\0 ch #\9)) (loop (+ (* n 10) (read-digit cur)) (fx1+ len))] + [(fx=? len 0) (error #f "Unexpected end of number, was expecting a fraction")] + [else (/ n (expt 10 len))])))) + 0))) + + (define (read-number-exponent cur) + (define (read-sign cur) + (let ([ch (pstring-cursor-peek-char cur)]) + (cond + [(eqv? ch #\+) (begin (pstring-cursor-read-char cur) 1)] + [(eqv? ch #\-) (begin (pstring-cursor-read-char cur) -1)] + [else 1]))) + + (let ([ch (pstring-cursor-peek-char cur)]) + (if (or (eqv? ch #\e) (eqv? ch #\E)) + (let* ([_ (pstring-cursor-read-char cur)] + [sign (read-sign cur)] + [digits (read-int cur)]) + (expt 10 (* sign digits))) + 1))) + + (define (read-json-number cur) + (let ([ch (pstring-cursor-peek-char cur)]) + (cond + [(eqv? ch #\0) + (begin + (pstring-cursor-read-char cur) + (read-number-fraction cur))] + [else + (let* ([num (read-int cur)] + [fraction (read-number-fraction cur)] + [exponent (read-number-exponent cur)]) + (* (+ num fraction) exponent))]))) + + (define (read-json-array cur) + (expect-char cur #\[) + (let loop ([items '()]) + (skip-whitespace cur) + (let ([ch (pstring-cursor-peek-char cur)]) + (cond + [(eqv? ch #\]) (begin + (pstring-cursor-read-char cur) + (srfi:214:list->flexvector (reverse items)))] + [(null? items) (loop (cons (read-json-value cur) items))] + [(eqv? ch #\,) (pstring-cursor-read-char cur) + (loop (cons (read-json-value cur) items))] + [else (error #f (format "Unexpected token '~a'" ch))])))) + + (define (read-json-object cur) + (define (read-object-pair cur) + (skip-whitespace cur) + (let ([key (read-json-string cur)]) + (skip-whitespace cur) + (expect-char cur #\:) + (cons (pstring->symbol key) (read-json-value cur)))) + + (expect-char cur #\{) + + (let loop ([pairs '()]) + (skip-whitespace cur) + (let ([ch (pstring-cursor-peek-char cur)]) + (cond + [(eqv? ch #\}) (begin + (pstring-cursor-read-char cur) + pairs)] + [(null? pairs) (loop (cons (read-object-pair cur) pairs))] + [(eqv? ch #\,) (pstring-cursor-read-char cur) + (loop (cons (read-object-pair cur) pairs))] + [else (error #f ("Expected comma or closing curly, got '~a'" ch))])))) + + (define (skip-whitespace cur) + (define (whitespace-char? ch) + (or (eqv? ch #\space) + (eqv? ch #\newline) + (eqv? ch #\return) + (eqv? ch #\tab))) + + (when (whitespace-char? (pstring-cursor-peek-char cur)) + (pstring-cursor-read-char cur) + (skip-whitespace cur))) + + ; --------------------------------------------------- + ; JSON printer + ; --------------------------------------------------- + + (define (json-stringify v) + (define (string-escape str) + (pstring-regex-replace-by + (pstring-make-regex (string->pstring "[\"\\\\\\b\\f\\r\\n\\t]") '((global . #t))) + str + (lambda (match _) + (let-values ([(ch _) (pstring-uncons-char match)]) + (cond + [(eqv? ch #\") (pstring #\\ #\")] + [(eqv? ch #\\) (pstring #\\ #\\)] + [(eqv? ch #\backspace) (pstring #\\ #\b)] + [(eqv? ch #\page) (pstring #\\ #\f)] + [(eqv? ch #\return) (pstring #\\ #\r)] + [(eqv? ch #\newline) (pstring #\\ #\n)] + [(eqv? ch #\tab) (pstring #\\ #\t)]))))) + (cond + [(pstring? v) (pstring-concat (pstring #\") (string-escape v) (pstring #\"))] + [(number? v) + (cond + [(ratnum? v) (number->pstring (inexact v))] + [else (number->pstring v)])] + [(boolean? v) (if v true false)] + [(eq? 'null v) null] + [(srfi:214:flexvector? v) + (pstring-concat + (pstring #\[) + (apply pstring-concat + (srfi:214:flexvector-fold-right + (lambda (acc next) + (cons (json-stringify next) + (if (null? acc) + acc + (cons (pstring #\,) acc)))) + '() + v)) + (pstring #\]))] + [(list? v) + (pstring-concat + (pstring #\{) + (apply pstring-concat + ; Parser reverses the keys, and so does this. + ; This keeps the original key order that was used when parsing. + (fold-left + (lambda (acc next) + (cons (json-stringify (symbol->pstring (car next))) + (cons (pstring #\:) + (cons (json-stringify (cdr next)) + (if (null? acc) + acc + (cons (pstring #\,) acc)))))) + '() + v)) + (pstring #\}))] + )) + + + ) diff --git a/json/src/JSON/Object.ss b/json/src/JSON/Object.ss new file mode 100644 index 00000000..43cbbfa9 --- /dev/null +++ b/json/src/JSON/Object.ss @@ -0,0 +1,7 @@ +(library (JSON.Object foreign) + (export empty) + (import (chezscheme)) + + (define empty '()) + + ) diff --git a/json/test/Main.purs b/json/test/Main.purs index d3f57e0f..b7f05edc 100644 --- a/json/test/Main.purs +++ b/json/test/Main.purs @@ -1,4 +1,4 @@ -module Test.Main where +module Test.JSON.Main where import Prelude @@ -12,6 +12,8 @@ import JSON.Object as JO import JSON.Path as Path import Test.Assert (assertTrue) +foreign import testJsonParser :: Effect Unit + main :: Effect Unit main = do @@ -35,6 +37,10 @@ main = do assertTrue $ J.fromJObject (JO.fromEntries [ Tuple "a" (J.fromInt 1) ]) == J.fromJObject (JO.fromEntries [ Tuple "a" (J.fromInt 1) ]) assertTrue $ J.fromJObject (JO.fromEntries [ Tuple "a" (J.fromInt 1) ]) < J.fromJObject (JO.fromEntries [ Tuple "a" (J.fromInt 2) ]) + log "Check isNull" + assertTrue $ J.isNull J.null + assertTrue $ not $ J.isNull (J.fromInt 1) + log "Check array index" assertTrue $ JA.index (-1) (JA.fromArray (J.fromInt <$> [ 0, 2, 4 ])) == Nothing assertTrue $ JA.index 0 (JA.fromArray (J.fromInt <$> [ 0, 2, 4 ])) == Just (J.fromInt 0) @@ -93,3 +99,9 @@ main = do let p1 = Path.AtKey "other" Path.Tip let p2 = Path.AtKey "y" $ Path.AtKey "x" $ Path.AtIndex 0 Path.Tip Path.stripPrefix p1 p2 == Nothing + + log "Check JSON parsing" + testJsonParser + assertTrue $ J.parse "[]" == pure (J.fromJArray (JA.fromArray [])) + let obj =J.fromJObject (JO.fromEntries [ Tuple "foo" (J.fromNumber 123.45), Tuple "bar" (J.fromString "Hello PS!"), Tuple "baz" J.null ]) + assertTrue $ J.parse "{ \"foo\": 123.45, \"bar\": \"Hello PS!\", \"baz\": null }" == pure obj diff --git a/json/test/Main.ss b/json/test/Main.ss new file mode 100644 index 00000000..2edaa8a3 --- /dev/null +++ b/json/test/Main.ss @@ -0,0 +1,153 @@ +(library (Test.JSON.Main foreign) + (export testJsonParser) + (import (chezscheme) + (prefix (purs runtime) rt:) + (prefix (purs runtime srfi :214) srfi:214:) + (purs runtime pstring) + (only (JSON.Internal foreign) json-parse json-stringify)) + + (define (assert-parsed input expected) + (let ([actual (json-parse (string->pstring input))]) + (when (not (pstring=? actual (string->pstring expected))) + (error #f (format "Expected ~s, got ~s" expected (pstring->string actual)))))) + + (define (assert-roundtrip input expected) + (let* ([actual (json-parse (string->pstring input))] + [actual-str (json-stringify actual)]) + (when (not (pstring=? actual-str (string->pstring expected))) + (error #f (format "Expected ~s, got ~s" expected (pstring->string actual-str)))))) + + (define check-raises + (case-lambda + [(thunk msg) + (let ([res (call/cc + (lambda (k) + (with-exception-handler + (lambda (e) (k e)) + (lambda () (begin (thunk) #f)))))]) + (if (not res) + (error #f "Expected to fail but did not") + (when (not (string=? msg (condition-message res))) + (error #f (format "Expected to fail with message ~s, but got ~s" msg (condition-message res))))))] + [(thunk) + (let ([res (call/cc + (lambda (k) + (with-exception-handler + (lambda (e) (k #t)) + (lambda () (begin (thunk) #f)))))]) + (if (not res) (error #f "Expected to fail but did not")))])) + + (define assert-fail + (case-lambda + [(input msg) (check-raises (lambda () (json-parse (string->pstring input))) msg)] + [(input) (check-raises (lambda () (json-parse (string->pstring input))))])) + + (define testJsonParser + (lambda () + (display " Strings\n") + (assert-roundtrip "\"foo\"" "\"foo\"") + + (assert-roundtrip "\"\\n\"" "\"\\n\"") + (assert-roundtrip "\"\\n\\t\\r\"" "\"\\n\\t\\r\"") + (assert-roundtrip "\"bar\\n\"" "\"bar\\n\"") + (assert-roundtrip "\"\\nbar\"" "\"\\nbar\"") + + (display " Invalid strings with control chars\n") + (assert-fail "\"\n\"" "Invalid control character in string") + (assert-fail "\"\r\"" "Invalid control character in string") + (assert-fail "\"foo\n\"" "Invalid control character in string") + (assert-fail "\"\\z\"" "Invalid string escape z") + + (display " Unicode escapes\n") + (assert-fail "\"\\u03\"") + (assert-roundtrip "\"\\u03BB\"" "\"λ\"") + (assert-roundtrip "\"foo \\u03BB \\u03bc\"" "\"foo λ μ\"") + (assert-fail "\"\\u03z\"" "Unexpected token 'z', was expecting a hex digit") + (assert-fail "\"\\u03" "Unexpected end of input, was expecting a hex digit") + + (display " Leading whitespace\n") + (assert-roundtrip "\n\r \"\\nbar\"" "\"\\nbar\"") + + (display " Numbers\n") + (assert-roundtrip " 0 " "0") + (assert-roundtrip "2" "2") + (assert-roundtrip "-2" "-2") + (assert-roundtrip "-123" "-123") + (assert-roundtrip "42" "42") + (assert-roundtrip "402" "402") + (assert-roundtrip " 777777777777777777777777 " "777777777777777777777777") + (assert-roundtrip " -777777777777777777777777 " "-777777777777777777777777") + (assert-roundtrip "1.1" "1.1") + (assert-roundtrip "1.10" "1.1") + (assert-roundtrip "99999.99" "99999.99") + (assert-roundtrip "0.123" "0.123") + (assert-roundtrip "0.33333" "0.33333") + (assert-roundtrip "0.00" "0") + (assert-roundtrip "-0.00" "0") + (assert-roundtrip "0.01" "0.01") + (assert-roundtrip "-0.01" "-0.01") + (assert-roundtrip "12.34e10" "123400000000") + (assert-roundtrip "12e10" "120000000000") + (assert-roundtrip "12e+10" "120000000000") + (assert-roundtrip "12e01" "120") + (assert-roundtrip "1e-53" "1e-53") + (assert-roundtrip "1.10e30" "1100000000000000000000000000000") + (assert-fail "0." "Unexpected end of number, was expecting a fraction") + (assert-fail "01" "Unexpected token '1'") + (assert-fail "1ee1" "Unexpected token 'e'") + (assert-fail "1x1" "Unexpected token 'x'") + + (display " Booleans\n") + (assert-roundtrip "true" "true") + (assert-roundtrip "false" "false") + (assert-roundtrip " true " "true") + (assert-roundtrip " false " "false") + (assert-fail " f") + (assert-fail " fals") + (assert-fail " t") + (assert-fail " tru") + + (display " null\n") + (assert-roundtrip "null" "null") + (assert-roundtrip " null " "null") + (assert-fail " n") + (assert-fail " nul") + + (display " Arrays\n") + (assert-roundtrip "[]" "[]") + (assert-roundtrip "[ ]" "[]") + (assert-roundtrip "[\"foo\"]" "[\"foo\"]") + (assert-roundtrip "[\"foo\",\"bar\" ]" "[\"foo\",\"bar\"]") + (assert-roundtrip " [ \"foo\" , \"bar\" ]" "[\"foo\",\"bar\"]") + (assert-roundtrip " [ \"foo\" , \"bar\", \"baz\" ]" "[\"foo\",\"bar\",\"baz\"]") + (assert-roundtrip " [ \"foo\", true, false ]" "[\"foo\",true,false]") + (assert-fail "[,]" "Unexpected token ','") + (assert-fail "[,,]" "Unexpected token ','") + (assert-fail "[\"foo\",]" "Unexpected token ']'") + (assert-fail "[ , \"foo\"]" "Unexpected token ','") + + (display " Objects\n") + (assert-roundtrip "{}" "{}") + (assert-roundtrip "{ }" "{}") + (assert-roundtrip "{ \"key\":\"value\"}" "{\"key\":\"value\"}") + (assert-roundtrip "{ \"key\" : \"value\" }" "{\"key\":\"value\"}") + (assert-roundtrip "{ \"key\":\"value\",\"key2\":\"value2\"}" "{\"key\":\"value\",\"key2\":\"value2\"}") + (assert-roundtrip "{ \"key\":\"value\",\"key2\":\"value2\",\"key3\":\"value3\"}" "{\"key\":\"value\",\"key2\":\"value2\",\"key3\":\"value3\"}") + (assert-fail "{,}") + (assert-fail "{\"foo\",}") + (assert-fail "{, \"foo\"}" "Unexpected ',', was expecting '\"'") + (assert-fail "{\"foo\" : }") + + (display " Nested\n") + (assert-roundtrip "{ \"array\":[ null, true, false ], \"obj\": { \"key\" : \"value\" }}" "{\"array\":[null,true,false],\"obj\":{\"key\":\"value\"}}") + + (display " Trailing token\n") + (assert-fail "0 0" "Unexpected token '0'") + (assert-fail "{} 0" "Unexpected token '0'") + (assert-fail "[] []" "Unexpected token '['") + (assert-fail "[][]" "Unexpected token '['") + + )) + + ) + diff --git a/spago.lock b/spago.lock index bb6ca5fb..ff1a9907 100644 --- a/spago.lock +++ b/spago.lock @@ -250,6 +250,60 @@ workspace: - prelude - safe-coerce - unsafe-coerce + json: + path: json + dependencies: + - arrays + - either + - foldable-traversable + - functions + - gen + - integers + - maybe + - prelude + - strings + - tuples + - unfoldable + test_dependencies: + - assert + build_plan: + - arrays + - assert + - bifunctors + - console + - const + - contravariant + - control + - distributive + - effect + - either + - enums + - exists + - foldable-traversable + - functions + - functors + - gen + - identity + - integers + - invariant + - maybe + - minibench + - newtype + - nonempty + - numbers + - orders + - partial + - prelude + - profunctor + - refs + - safe-coerce + - st + - strings + - tailrec + - tuples + - type-equality + - unfoldable + - unsafe-coerce lazy: path: lazy dependencies: diff --git a/test.sh b/test.sh index d35836bb..3ca090cb 100755 --- a/test.sh +++ b/test.sh @@ -30,6 +30,10 @@ echo "Testing integers" purescm run --main Test.Int.Main echo +echo "Testing json" +purescm run --main Test.JSON.Main +echo + echo "Testing lazy" purescm run --main Test.Lazy.Main echo @@ -56,8 +60,10 @@ echo echo "Testing record" purescm run --main Test.Record.Main +echo echo "Testing quickcheck" purescm run --main Test.QuickCheck.Main +echo echo "All good!"