diff options
Diffstat (limited to 'stdlib/source/lux/data/format/json.lux')
-rw-r--r-- | stdlib/source/lux/data/format/json.lux | 1031 |
1 files changed, 1031 insertions, 0 deletions
diff --git a/stdlib/source/lux/data/format/json.lux b/stdlib/source/lux/data/format/json.lux new file mode 100644 index 000000000..c51e4b04c --- /dev/null +++ b/stdlib/source/lux/data/format/json.lux @@ -0,0 +1,1031 @@ +## Copyright (c) Eduardo Julian. All rights reserved. +## This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +## If a copy of the MPL was not distributed with this file, +## You can obtain one at http://mozilla.org/MPL/2.0/. + +(;module: + lux + (lux (control functor + applicative + monad + eq + codec) + (data [bool] + [text "Text/" Eq<Text> Monoid<Text>] + text/format + [number #* "Real/" Codec<Text,Real>] + maybe + [char "Char/" Eq<Char> Codec<Text,Char>] + error + [sum] + [product] + (struct [list "" Fold<List> "List/" Monad<List>] + [vector #+ Vector vector "Vector/" Monad<Vector>] + [dict #+ Dict])) + (codata [function]) + [compiler #+ Monad<Lux> with-gensyms] + (macro [syntax #+ syntax:] + [ast] + [poly #+ poly:]) + [type] + [lexer #+ Lexer Monad<Lexer>])) + +## [Types] +(do-template [<name> <type>] + [(type: #export <name> <type>)] + + [Null Unit] + [Boolean Bool] + [Number Real] + [String Text] + ) + +(type: #export #rec JSON + (#Null Null) + (#Boolean Boolean) + (#Number Number) + (#String String) + (#Array (Vector JSON)) + (#Object (Dict String JSON))) + +(do-template [<name> <type>] + [(type: #export <name> <type>)] + + [Array (Vector JSON)] + [Object (Dict String JSON)] + ) + +(type: #export (Parser a) + (-> JSON (Error a))) + +(type: #export (Gen a) + (-> a JSON)) + +## [Syntax] +(syntax: #export (json token) + (let [(^open) Monad<Lux> + wrapper (lambda [x] (` (;;json (~ x))))] + (case token + (^template [<ast-tag> <ctor> <json-tag>] + [_ (<ast-tag> value)] + (wrap (list (` (: JSON (<json-tag> (~ (<ctor> value)))))))) + ([#;BoolS ast;bool #Boolean] + [#;IntS (|>. int-to-real ast;real) #Number] + [#;RealS ast;real #Number] + [#;TextS ast;text #String]) + + [_ (#;TagS ["" "null"])] + (wrap (list (` (: JSON #Null)))) + + [_ (#;TupleS members)] + (wrap (list (` (: JSON (#Array (vector (~@ (List/map wrapper members)))))))) + + [_ (#;RecordS pairs)] + (do Monad<Lux> + [pairs' (mapM @ + (lambda [[slot value]] + (case slot + [_ (#;TextS key-name)] + (wrap (` [(~ (ast;text key-name)) (~ (wrapper value))])) + + _ + (compiler;fail "Wrong syntax for JSON object."))) + pairs)] + (wrap (list (` (: JSON (#Object (dict;from-list text;Hash<Text> (list (~@ pairs'))))))))) + + _ + (wrap (list token)) + ))) + +## [Values] +(def: #hidden (show-null _) (-> Null Text) "null") +(do-template [<name> <type> <codec>] + [(def: <name> (-> <type> Text) (:: <codec> encode))] + + [show-boolean Boolean bool;Codec<Text,Bool>] + [show-number Number number;Codec<Text,Real>] + [show-string String text;Codec<Text,Text>]) + +(def: (show-array show-json elems) + (-> (-> JSON Text) (-> Array Text)) + (format "[" + (|> elems (Vector/map show-json) vector;vector-to-list (text;join-with ",")) + "]")) + +(def: (show-object show-json object) + (-> (-> JSON Text) (-> Object Text)) + (format "{" + (|> object + dict;entries + (List/map (lambda [[key value]] (format (:: text;Codec<Text,Text> encode key) ":" (show-json value)))) + (text;join-with ",")) + "}")) + +(def: (show-json json) + (-> JSON Text) + (case json + (^template [<tag> <show>] + (<tag> value) + (<show> value)) + ([#Null show-null] + [#Boolean show-boolean] + [#Number show-number] + [#String show-string] + [#Array (show-array show-json)] + [#Object (show-object show-json)]) + )) + +(def: #export null + JSON + #Null) + +(def: #export (keys json) + (-> JSON (Error (List String))) + (case json + (#Object obj) + (#;Right (dict;keys obj)) + + _ + (#;Left (format "Can't get keys of a non-object.")))) + +(def: #export (get key json) + (-> String JSON (Error JSON)) + (case json + (#Object obj) + (case (dict;get key obj) + (#;Some value) + (#;Right value) + + #;None + (#;Left (format "Missing field " (show-string key) " on object."))) + + _ + (#;Left (format "Can't get field " (show-string key) " of a non-object.")))) + +(def: #export (set key value json) + (-> String JSON JSON (Error JSON)) + (case json + (#Object obj) + (#;Right (#Object (dict;put key value obj))) + + _ + (#;Left (format "Can't set field " (show-string key) " of a non-object.")))) + +(do-template [<name> <tag> <type>] + [(def: #export (<name> key json) + (-> Text JSON (Error <type>)) + (case (get key json) + (#;Right (<tag> value)) + (#;Right value) + + (#;Right _) + (#;Left (format "Wrong value type at key " (show-string key))) + + (#;Left error) + (#;Left error)))] + + [get-boolean #Boolean Boolean] + [get-number #Number Number] + [get-string #String String] + [get-array #Array Array] + [get-object #Object Object] + ) + +(do-template [<name> <type> <tag>] + [(def: #export (<name> value) + (Gen <type>) + (<tag> value))] + + [gen-boolean Boolean #Boolean] + [gen-number Number #Number] + [gen-string String #String] + [gen-array Array #Array] + [gen-object Object #Object] + ) + +(def: #export (gen-nullable gen) + (All [a] (-> (Gen a) (Gen (Maybe a)))) + (lambda [elem] + (case elem + #;None #Null + (#;Some value) (gen value)))) + +## Lexers +(def: space~ + (Lexer Text) + (lexer;some' lexer;space)) + +(def: data-sep + (Lexer [Text Char Text]) + ($_ lexer;seq space~ (lexer;this-char #",") space~)) + +(def: null~ + (Lexer Null) + (do Monad<Lexer> + [_ (lexer;this "null")] + (wrap []))) + +(do-template [<name> <token> <value>] + [(def: <name> + (Lexer Boolean) + (do Monad<Lexer> + [_ (lexer;this <token>)] + (wrap <value>)))] + + [t~ "true" true] + [f~ "false" false] + ) + +(def: boolean~ + (Lexer Boolean) + (lexer;either t~ f~)) + +(def: number~ + (Lexer Number) + (do Monad<Lexer> + [?sign (: (Lexer (Maybe Text)) + (lexer;opt (lexer;this "-"))) + digits (: (Lexer Text) + (lexer;many' lexer;digit)) + ?decimals (: (Lexer (Maybe Text)) + (lexer;opt (do @ + [_ (lexer;this ".")] + (lexer;many' lexer;digit))))] + (case (: (Error Real) + (Real/decode (format (default "" ?sign) + digits "." + (default "0" ?decimals)))) + (#;Left message) + (lexer;fail message) + + (#;Right value) + (wrap value)))) + +(def: (un-escape escaped) + (-> Char Text) + (case escaped + #"t" "\t" + #"b" "\b" + #"n" "\n" + #"r" "\r" + #"f" "\f" + #"\"" "\"" + #"\\" "\\" + _ "")) + +(def: string-body~ + (Lexer Text) + (loop [_ []] + (do Monad<Lexer> + [chars (lexer;some' (lexer;none-of "\\\"")) + stop-char lexer;peek] + (if (Char/= #"\\" stop-char) + (do @ + [_ lexer;any + escaped lexer;any + next-chars (recur [])] + (wrap (format chars (un-escape escaped) next-chars))) + (wrap chars))))) + +(def: string~ + (Lexer String) + (do Monad<Lexer> + [_ (lexer;this "\"") + string-body string-body~ + _ (lexer;this "\"")] + (wrap string-body))) + +(def: (kv~ json~) + (-> (-> Unit (Lexer JSON)) (Lexer [String JSON])) + (do Monad<Lexer> + [key string~ + _ space~ + _ (lexer;this-char #":") + _ space~ + value (json~ [])] + (wrap [key value]))) + +(do-template [<name> <type> <open> <close> <elem-parser> <prep>] + [(def: (<name> json~) + (-> (-> Unit (Lexer JSON)) (Lexer <type>)) + (do Monad<Lexer> + [_ (lexer;this-char <open>) + _ space~ + elems (lexer;sep-by data-sep <elem-parser>) + _ space~ + _ (lexer;this-char <close>)] + (wrap (<prep> elems))))] + + [array~ Array #"[" #"]" (json~ []) vector;list-to-vector] + [object~ Object #"{" #"}" (kv~ json~) (dict;from-list text;Hash<Text>)] + ) + +(def: (json~' _) + (-> Unit (Lexer JSON)) + ($_ lexer;alt null~ boolean~ number~ string~ (array~ json~') (object~ json~'))) + +## [Structures] +(struct: #export _ (Functor Parser) + (def: (map f ma) + (lambda [json] + (case (ma json) + (#;Left msg) + (#;Left msg) + + (#;Right a) + (#;Right (f a)))))) + +(struct: #export _ (Applicative Parser) + (def: functor Functor<Parser>) + + (def: (wrap x json) + (#;Right x)) + + (def: (apply ff fa) + (lambda [json] + (case (ff json) + (#;Right f) + (case (fa json) + (#;Right a) + (#;Right (f a)) + + (#;Left msg) + (#;Left msg)) + + (#;Left msg) + (#;Left msg))))) + +(struct: #export _ (Monad Parser) + (def: applicative Applicative<Parser>) + + (def: (join mma) + (lambda [json] + (case (mma json) + (#;Left msg) + (#;Left msg) + + (#;Right ma) + (ma json))))) + +## [Values] +## Syntax +(do-template [<name> <type> <tag> <desc> <pre>] + [(def: #export (<name> json) + (Parser <type>) + (case json + (<tag> value) + (#;Right (<pre> value)) + + _ + (#;Left (format "JSON value is not a " <desc> ": " (show-json json)))))] + + [unit Unit #Null "null" id] + [bool Bool #Boolean "boolean" id] + [int Int #Number "number" real-to-int] + [real Real #Number "number" id] + [text Text #String "string" id] + ) + +(do-template [<test> <check> <type> <eq> <codec> <tag> <desc> <pre>] + [(def: #export (<test> test json) + (-> <type> (Parser Bool)) + (case json + (<tag> value) + (#;Right (:: <eq> = test (<pre> value))) + + _ + (#;Left (format "JSON value is not a " <desc> ": " (show-json json))))) + + (def: #export (<check> test json) + (-> <type> (Parser Unit)) + (case json + (<tag> value) + (let [value (<pre> value)] + (if (:: <eq> = test value) + (#;Right []) + (#;Left (format "Value mismatch: " + (:: <codec> encode test) "=/=" (:: <codec> encode value))))) + + _ + (#;Left (format "JSON value is not a " <desc> ": " (show-json json)))))] + + [bool? bool! Bool bool;Eq<Bool> bool;Codec<Text,Bool> #Boolean "boolean" id] + [int? int! Int number;Eq<Int> number;Codec<Text,Int> #Number "number" real-to-int] + [real? real! Real number;Eq<Real> number;Codec<Text,Real> #Number "number" id] + [text? text! Text text;Eq<Text> text;Codec<Text,Text> #String "string" id] + ) + +(def: #export (char json) + (Parser Char) + (case json + (#String input) + (case (Char/decode (format "#\"" input "\"")) + (#;Right value) + (#;Right value) + + (#;Left _) + (#;Left (format "Invalid format for char: " input))) + + _ + (#;Left (format "JSON value is not a " "string" ": " (show-json json))))) + +(def: #export (char? test json) + (-> Char (Parser Bool)) + (case json + (#String input) + (case (Char/decode (format "#\"" input "\"")) + (#;Right value) + (if (:: char;Eq<Char> = test value) + (#;Right true) + (#;Left (format "Value mismatch: " + (:: char;Codec<Text,Char> encode test) "=/=" (:: char;Codec<Text,Char> encode value)))) + + (#;Left _) + (#;Left (format "Invalid format for char: " input))) + + _ + (#;Left (format "JSON value is not a " "string" ": " (show-json json))))) + +(def: #export (char! test json) + (-> Char (Parser Unit)) + (case json + (#String input) + (case (Char/decode (format "#\"" input "\"")) + (#;Right value) + (if (:: char;Eq<Char> = test value) + (#;Right []) + (#;Left (format "Value mismatch: " + (:: char;Codec<Text,Char> encode test) "=/=" (:: char;Codec<Text,Char> encode value)))) + + (#;Left _) + (#;Left (format "Invalid format for char: " input))) + + _ + (#;Left (format "JSON value is not a " "string" ": " (show-json json))))) + +(def: #export (nullable parser) + (All [a] (-> (Parser a) (Parser (Maybe a)))) + (lambda [json] + (case json + #Null + (#;Right #;None) + + _ + (case (parser json) + (#;Left error) + (#;Left error) + + (#;Right value) + (#;Right (#;Some value))) + ))) + +(def: #export (array parser) + (All [a] (-> (Parser a) (Parser (List a)))) + (lambda [json] + (case json + (#Array values) + (do Monad<Error> + [elems (mapM @ parser (vector;vector-to-list values))] + (wrap elems)) + + _ + (#;Left (format "JSON value is not an array: " (show-json json)))))) + +(def: #export (object parser) + (All [a] (-> (Parser a) (Parser (Dict String a)))) + (lambda [json] + (case json + (#Object fields) + (do Monad<Error> + [kvs (mapM @ + (lambda [[key val']] + (do @ + [val (parser val')] + (wrap [key val]))) + (dict;entries fields))] + (wrap (dict;from-list text;Hash<Text> kvs))) + + _ + (#;Left (format "JSON value is not an object: " (show-json json)))))) + +(def: #export (at idx parser) + (All [a] (-> Nat (Parser a) (Parser a))) + (lambda [json] + (case json + (#Array values) + (case (vector;at idx values) + (#;Some value) + (case (parser value) + (#;Right output) + (#;Right output) + + (#;Left error) + (#;Left (format "JSON array index [" (%n idx) "]: (" error ") @ " (show-json json)))) + + #;None + (#;Left (format "JSON array does not have index " (%n idx) " @ " (show-json json)))) + + _ + (#;Left (format "JSON value is not an array: " (show-json json)))))) + +(def: #export (field field-name parser) + (All [a] (-> Text (Parser a) (Parser a))) + (lambda [json] + (case (get field-name json) + (#;Some value) + (case (parser value) + (#;Right output) + (#;Right output) + + (#;Left error) + (#;Left (format "Failed to get JSON object field " (show-string field-name) ": (" error ") @ " (show-json json)))) + + (#;Left _) + (#;Left (format "JSON object does not have field " (show-string field-name) " @ " (show-json json)))))) + +(def: #export any + (Parser JSON) + (lambda [json] + (#;Right json))) + +(def: #export (seq pa pb) + (All [a b] (-> (Parser a) (Parser b) (Parser [a b]))) + (do Monad<Parser> + [=a pa + =b pb] + (wrap [=a =b]))) + +(def: #export (alt pa pb json) + (All [a b] (-> (Parser a) (Parser b) (Parser (| a b)))) + (case (pa json) + (#;Right a) + (sum;right (sum;left a)) + + (#;Left message0) + (case (pb json) + (#;Right b) + (sum;right (sum;right b)) + + (#;Left message1) + (#;Left message0)))) + +(def: #export (either pl pr json) + (All [a] (-> (Parser a) (Parser a) (Parser a))) + (case (pl json) + (#;Right x) + (#;Right x) + + _ + (pr json))) + +(def: #export (opt p json) + (All [a] + (-> (Parser a) (Parser (Maybe a)))) + (case (p json) + (#;Left _) (#;Right #;None) + (#;Right x) (#;Right (#;Some x)))) + +(def: #export (run parser json) + (All [a] (-> (Parser a) JSON (Error a))) + (parser json)) + +(def: #export (ensure test parser json) + (All [a] (-> (Parser Unit) (Parser a) (Parser a))) + (case (test json) + (#;Right _) + (parser json) + + (#;Left error) + (#;Left error))) + +(def: #export (array-size! array-size json) + (-> Nat (Parser Unit)) + (case json + (#Array parts) + (if (=+ array-size (vector;size parts)) + (#;Right []) + (#;Left (format "JSON array does no have size " (%n array-size) " " (show-json json)))) + + _ + (#;Left (format "JSON value is not an array: " (show-json json))))) + +(def: #export (object-fields! wanted-fields json) + (-> (List String) (Parser Unit)) + (case json + (#Object kvs) + (let [actual-fields (dict;keys kvs)] + (if (and (=+ (list;size wanted-fields) (list;size actual-fields)) + (list;every? (list;member? text;Eq<Text> wanted-fields) + actual-fields)) + (#;Right []) + (#;Left (format "JSON object has wrong field-set. Expected: [" (text;join-with ", " wanted-fields) "]. Actual: [" (text;join-with ", " actual-fields) "]")))) + + _ + (#;Left (format "JSON value is not an object: " (show-json json))))) + +## [Structures] +(struct: #export _ (Eq JSON) + (def: (= x y) + (case [x y] + [#Null #Null] + true + + (^template [<tag> <struct>] + [(<tag> x') (<tag> y')] + (:: <struct> = x' y')) + ([#Boolean bool;Eq<Bool>] + [#Number number;Eq<Real>] + [#String text;Eq<Text>]) + + [(#Array xs) (#Array ys)] + (and (=+ (vector;size xs) (vector;size ys)) + (fold (lambda [idx prev] + (and prev + (default false + (do Monad<Maybe> + [x' (vector;at idx xs) + y' (vector;at idx ys)] + (wrap (= x' y')))))) + true + (list;indices (vector;size xs)))) + + [(#Object xs) (#Object ys)] + (and (=+ (dict;size xs) (dict;size ys)) + (fold (lambda [[xk xv] prev] + (and prev + (case (dict;get xk ys) + #;None false + (#;Some yv) (= xv yv)))) + true + (dict;entries xs))) + + _ + false))) + +(struct: #export _ (Codec Text JSON) + (def: encode show-json) + (def: decode (lexer;run (json~' [])))) + +## [Syntax] +(type: Shape + (#ArrayShape (List AST)) + (#ObjectShape (List [Text AST]))) + +(def: _shape^ + (syntax;Syntax Shape) + (syntax;alt (syntax;tuple (syntax;some syntax;any)) + (syntax;record (syntax;some (syntax;seq syntax;text syntax;any))))) + +(syntax: #export (shape^ {shape _shape^}) + (case shape + (#ArrayShape parts) + (let [array-size (list;size parts) + parsers (|> parts + (list;zip2 (list;indices array-size)) + (List/map (lambda [[idx parser]] + (` (at (~ (ast;nat idx)) (~ parser))))))] + (wrap (list (` ($_ seq (~@ parsers)))))) + + (#ObjectShape kvs) + (let [fields (List/map product;left kvs) + parsers (List/map (lambda [[field-name parser]] + (` (field (~ (ast;text field-name)) (~ parser)))) + kvs)] + (wrap (list (` ($_ seq (~@ parsers)))))) + )) + +(syntax: #export (shape!^ {shape _shape^}) + (case shape + (#ArrayShape parts) + (let [array-size (list;size parts) + parsers (|> parts + (list;zip2 (list;indices array-size)) + (List/map (lambda [[idx parser]] + (` (at (~ (ast;nat idx)) (~ parser))))))] + (wrap (list (` (ensure (array-size! (~ (ast;nat array-size))) + ($_ seq (~@ parsers))))))) + + (#ObjectShape kvs) + (let [fields (List/map product;left kvs) + parsers (List/map (lambda [[field-name parser]] + (` (field (~ (ast;text field-name)) (~ parser)))) + kvs)] + (wrap (list (` (ensure (object-fields! (list (~@ (List/map ast;text fields)))) + ($_ seq (~@ parsers))))))) + )) + +## [Polytypism] +(def: #hidden _map_ + (All [a b] (-> (-> a b) (List a) (List b))) + List/map) + +(poly: #export (|Codec@JSON//encode| *env* :x:) + (let [->Codec//encode (: (-> AST AST) + (lambda [.type.] (` (-> (~ .type.) JSON))))] + (let% [<basic> (do-template [<type> <matcher> <encoder>] + [(do @ [_ (<matcher> :x:)] (wrap (` (: (~ (->Codec//encode (` <type>))) <encoder>))))] + + [Unit poly;unit (lambda [(~ (ast;symbol ["" "0"]))] #Null)] + [Bool poly;bool ;;boolean] + [Int poly;int (|>. int-to-real ;;number)] + [Real poly;real ;;number] + [Char poly;char (|>. char;->Text ;;string)] + [Text poly;text ;;string])] + ($_ compiler;either + <basic> + (with-gensyms [g!type-fun g!case g!input g!key g!val] + (do @ + [:sub: (poly;list :x:) + [g!vars members] (poly;tuple :sub:) + :val: (case members + (^ (list :key: :val:)) + (do @ [_ (poly;text :key:)] + (wrap :val:)) + + _ + (compiler;fail "")) + #let [new-*env* (poly;extend-env g!type-fun g!vars *env*)] + .val. (|Codec@JSON//encode| new-*env* :val:) + #let [:x:+ (case g!vars + #;Nil + (->Codec//encode (type;type-to-ast :x:)) + + _ + (` (All (~ g!type-fun) [(~@ g!vars)] + (-> (~@ (List/map ->Codec//encode g!vars)) + (~ (->Codec//encode (` ((~ (type;type-to-ast :x:)) (~@ g!vars)))))))))]] + (wrap (` (: (~ :x:+) + (lambda [(~@ g!vars) (~ g!input)] + (|> (~ g!input) + (_map_ (: (-> [Text (~ (type;type-to-ast :val:))] + [Text JSON]) + (lambda [[(~ g!key) (~ g!val)]] + [(~ g!key) + ((~ .val.) (~ g!val))]))) + ;;object)) + ))) + )) + (do @ + [:sub: (poly;maybe :x:) + .sub. (|Codec@JSON//encode| *env* :sub:)] + (wrap (` (: (~ (->Codec//encode (type;type-to-ast :x:))) + (;;nullable (~ .sub.)))))) + (do @ + [:sub: (poly;list :x:) + .sub. (|Codec@JSON//encode| *env* :sub:)] + (wrap (` (: (~ (->Codec//encode (type;type-to-ast :x:))) + (|>. (_map_ (~ .sub.)) vector;list-to-vector ;;array))))) + (with-gensyms [g!type-fun g!case g!input] + (do @ + [[g!vars cases] (poly;variant :x:) + #let [new-*env* (poly;extend-env g!type-fun g!vars *env*)] + pattern-matching (mapM @ + (lambda [[name :case:]] + (do @ + [#let [tag (ast;tag name)] + encoder (|Codec@JSON//encode| new-*env* :case:)] + (wrap (list (` ((~ tag) (~ g!case))) + (` (;;json [(~ (ast;text (product;right name))) + ((~ encoder) (~ g!case))])))))) + cases) + #let [:x:+ (case g!vars + #;Nil + (->Codec//encode (type;type-to-ast :x:)) + + _ + (` (All (~ g!type-fun) [(~@ g!vars)] + (-> (~@ (List/map ->Codec//encode g!vars)) + (~ (->Codec//encode (` ((~ (type;type-to-ast :x:)) (~@ g!vars)))))))))]] + (wrap (` (: (~ :x:+) + (lambda [(~@ g!vars) (~ g!input)] + (case (~ g!input) + (~@ (List/join pattern-matching)))) + ))))) + (with-gensyms [g!type-fun g!case g!input] + (do @ + [[g!vars slots] (poly;record :x:) + #let [new-*env* (poly;extend-env g!type-fun g!vars *env*)] + synthesis (mapM @ + (lambda [[name :slot:]] + (do @ + [encoder (|Codec@JSON//encode| new-*env* :slot:)] + (wrap [(` (~ (ast;text (product;right name)))) + (` ((~ encoder) (get@ (~ (ast;tag name)) (~ g!input))))]))) + slots) + #let [:x:+ (case g!vars + #;Nil + (->Codec//encode (type;type-to-ast :x:)) + + _ + (` (All (~ g!type-fun) [(~@ g!vars)] + (-> (~@ (List/map ->Codec//encode g!vars)) + (~ (->Codec//encode (` ((~ (type;type-to-ast :x:)) (~@ g!vars)))))))))]] + (wrap (` (: (~ :x:+) + (lambda [(~@ g!vars) (~ g!input)] + (;;json (~ (ast;record synthesis)))) + ))))) + (with-gensyms [g!type-fun g!case g!input] + (do @ + [[g!vars members] (poly;tuple :x:) + #let [new-*env* (poly;extend-env g!type-fun g!vars *env*)] + pattern-matching (mapM @ + (lambda [:member:] + (do @ + [g!member (compiler;gensym "g!member") + encoder (|Codec@JSON//encode| new-*env* :member:)] + (wrap [g!member encoder]))) + members) + #let [:x:+ (case g!vars + #;Nil + (->Codec//encode (type;type-to-ast :x:)) + + _ + (` (All (~ g!type-fun) [(~@ g!vars)] + (-> (~@ (List/map ->Codec//encode g!vars)) + (~ (->Codec//encode (` ((~ (type;type-to-ast :x:)) (~@ g!vars)))))))))] + #let [.tuple. (` [(~@ (List/map product;left pattern-matching))])]] + (wrap (` (: (~ :x:+) + (lambda [(~@ g!vars) (~ g!input)] + (case (~ g!input) + (~ .tuple.) + (;;array (list (~@ (List/map (lambda [[g!member g!encoder]] + (` ((~ g!encoder) (~ g!member)))) + pattern-matching)))))) + ))) + )) + (do @ + [[:func: :args:] (poly;apply :x:) + .func. (|Codec@JSON//encode| *env* :func:) + .args. (mapM @ (|Codec@JSON//encode| *env*) :args:)] + (wrap (` (: (~ (->Codec//encode (type;type-to-ast :x:))) + ((~ .func.) (~@ .args.)))))) + (poly;bound *env* :x:) + (compiler;fail (format "Can't create JSON encoder for: " (type;type-to-text :x:))) + )))) + +(poly: #export (Codec<JSON,?>//decode *env* :x:) + (let [->Codec//decode (: (-> AST AST) + (lambda [.type.] (` (-> JSON (Error (~ .type.))))))] + (let% [<basic> (do-template [<type> <matcher> <decoder>] + [(do @ [_ (<matcher> :x:)] (wrap (` (: (~ (->Codec//decode (` <type>))) <decoder>))))] + + [Unit poly;unit ;;null] + [Bool poly;bool ;;bool] + [Int poly;int ;;int] + [Real poly;real ;;real] + [Char poly;char ;;char] + [Text poly;text ;;text]) + <complex> (do-template [<type> <matcher> <decoder>] + [(do @ + [:sub: (<matcher> :x:) + .sub. (Codec<JSON,?>//decode *env* :sub:)] + (wrap (` (: (~ (->Codec//decode (type;type-to-ast :x:))) + (<decoder> (~ .sub.))))))] + + [Maybe poly;maybe ;;nullable] + [List poly;list ;;array])] + ($_ compiler;either + <basic> + (with-gensyms [g!type-fun g!case g!input g!key g!val] + (do @ + [:sub: (poly;list :x:) + [g!vars members] (poly;tuple :sub:) + :val: (case members + (^ (list :key: :val:)) + (do @ [_ (poly;text :key:)] + (wrap :val:)) + + _ + (compiler;fail "")) + #let [new-*env* (poly;extend-env g!type-fun g!vars *env*)] + .val. (Codec<JSON,?>//decode new-*env* :val:) + #let [:x:+ (case g!vars + #;Nil + (->Codec//decode (type;type-to-ast :x:)) + + _ + (` (All (~ g!type-fun) [(~@ g!vars)] + (-> (~@ (List/map ->Codec//decode g!vars)) + (~ (->Codec//decode (` ((~ (type;type-to-ast :x:)) (~@ g!vars)))))))))]] + (wrap (` (: (~ :x:+) + (lambda [(~@ g!vars) (~ g!input)] + (do Monad<Error> + [(~ g!key) (;;keys (~ g!input))] + (mapM (~ (' %)) + (lambda [(~ g!key)] + (do Monad<Error> + [(~ g!val) (;;get (~ g!key) (~ g!input)) + (~ g!val) (;;run (~ .val.) (~ g!val))] + ((~ (' wrap)) [(~ g!key) (~ g!val)]))) + (~ g!key)))) + ))) + )) + <complex> + (with-gensyms [g!type-fun g!_] + (do @ + [[g!vars cases] (poly;variant :x:) + #let [new-*env* (poly;extend-env g!type-fun g!vars *env*)] + pattern-matching (mapM @ + (lambda [[name :case:]] + (do @ + [#let [tag (ast;tag name)] + decoder (Codec<JSON,?>//decode new-*env* :case:)] + (wrap (list (` (do Monad<Parser> + [(~ g!_) (;;at 0 (;;text! (~ (ast;text (product;right name))))) + (~ g!_) (;;at 1 (~ decoder))] + ((~ (' wrap)) ((~ tag) (~ g!_))))))))) + cases) + #let [:x:+ (case g!vars + #;Nil + (->Codec//decode (type;type-to-ast :x:)) + + _ + (` (All (~ g!type-fun) [(~@ g!vars)] + (-> (~@ (List/map ->Codec//decode g!vars)) + (~ (->Codec//decode (` ((~ (type;type-to-ast :x:)) (~@ g!vars))))))))) + base-parser (` ($_ ;;either + (~@ (List/join pattern-matching)))) + parser (case g!vars + #;Nil + base-parser + + _ + (` (lambda [(~@ g!vars)] (~ base-parser))))]] + (wrap (` (: (~ :x:+) (~ parser)))) + )) + (with-gensyms [g!type-fun g!case g!input] + (do @ + [[g!vars slots] (poly;record :x:) + #let [new-*env* (poly;extend-env g!type-fun g!vars *env*)] + extraction (mapM @ + (lambda [[name :slot:]] + (do @ + [#let [g!member (ast;symbol ["" (product;right name)])] + decoder (Codec<JSON,?>//decode new-*env* :slot:)] + (wrap (list g!member + (` (;;get (~ (ast;text (product;right name))) (~ g!input))) + g!member + (` ((~ decoder) (~ g!member))))))) + slots) + #let [:x:+ (case g!vars + #;Nil + (->Codec//decode (type;type-to-ast :x:)) + + _ + (` (All (~ g!type-fun) [(~@ g!vars)] + (-> (~@ (List/map ->Codec//decode g!vars)) + (~ (->Codec//decode (` ((~ (type;type-to-ast :x:)) (~@ g!vars)))))))))]] + (wrap (` (: (~ :x:+) + (lambda [(~@ g!vars) (~ g!input)] + (do Monad<Error> + [(~@ (List/join extraction))] + ((~ (' wrap)) (~ (ast;record (List/map (lambda [[name :slot:]] + [(ast;tag name) (ast;symbol ["" (product;right name)])]) + slots)))))) + ))))) + (with-gensyms [g!type-fun g!case g!input] + (do @ + [[g!vars members] (poly;tuple :x:) + #let [new-*env* (poly;extend-env g!type-fun g!vars *env*)] + pattern-matching (mapM @ + (lambda [:member:] + (do @ + [g!member (compiler;gensym "g!member") + decoder (Codec<JSON,?>//decode new-*env* :member:)] + (wrap [g!member decoder]))) + members) + #let [:x:+ (case g!vars + #;Nil + (->Codec//decode (type;type-to-ast :x:)) + + _ + (` (All (~ g!type-fun) [(~@ g!vars)] + (-> (~@ (List/map ->Codec//decode g!vars)) + (~ (->Codec//decode (` ((~ (type;type-to-ast :x:)) (~@ g!vars)))))))))] + #let [.decoder. (case g!vars + #;Nil + (` (;;shape^ [(~@ (List/map product;right pattern-matching))])) + + _ + (` (lambda [(~@ g!vars)] + (;;shape^ [(~@ (List/map product;right pattern-matching))]))))]] + (wrap (` (: (~ :x:+) (~ .decoder.)))) + )) + (do @ + [[:func: :args:] (poly;apply :x:) + .func. (Codec<JSON,?>//decode *env* :func:) + .args. (mapM @ (Codec<JSON,?>//decode *env*) :args:)] + (wrap (` (: (~ (->Codec//decode (type;type-to-ast :x:))) + ((~ .func.) (~@ .args.)))))) + (do @ + [g!bound (poly;bound *env* :x:)] + (wrap g!bound)) + (compiler;fail (format "Can't create JSON decoder for: " (type;type-to-text :x:))) + )))) + +(syntax: #export (Codec<JSON,?> :x:) + (wrap (list (` (: (Codec JSON (~ :x:)) + (struct + (def: (~ (' encode)) (|Codec@JSON//encode| (~ :x:))) + (def: (~ (' decode)) (Codec<JSON,?>//decode (~ :x:))) + )))))) |