From 6e9c6cfa02fa5fcb58ff741a23369e2a5372bc69 Mon Sep 17 00:00:00 2001 From: Tomi Jaga Date: Sun, 18 Dec 2022 13:39:05 -0500 Subject: [PATCH 1/6] add Float support --- src/JSON.mo | 172 +++++++++++++++++++++++++++++++++----------------- test/Show.mo | 21 +++--- test/Value.mo | 88 +++++++++++++++----------- 3 files changed, 178 insertions(+), 103 deletions(-) diff --git a/src/JSON.mo b/src/JSON.mo index 7dee324..9fffb26 100644 --- a/src/JSON.mo +++ b/src/JSON.mo @@ -2,6 +2,7 @@ import Char "mo:base-0.7.3/Char"; import Iter "mo:base-0.7.3/Iter"; import Int "mo:base-0.7.3/Int"; import List "mo:base-0.7.3/List"; +import Float "mo:base-0.7.3/Float"; import Nat8 "mo:base-0.7.3/Nat8"; import Nat32 "mo:base-0.7.3/Nat32"; import Result "mo:base-0.7.3/Result"; @@ -12,8 +13,11 @@ import L "mo:parser-combinators/List"; import P "mo:parser-combinators/Parser"; module JSON { + type List = List.List; + public type JSON = { - #Number : Int; // TODO: float + #Number : Int; + #Float : Float; #String : Text; #Array : [JSON]; #Object : [(Text, JSON)]; @@ -22,12 +26,13 @@ module JSON { }; public func show(json : JSON) : Text = switch (json) { - case (#Number(v)) { Int.toText(v); }; - case (#String(v)) { "\"" # v # "\""; }; + case (#Number(v)) { Int.toText(v) }; + case (#Float(v)) { Float.toText(v) }; + case (#String(v)) { "\"" # v # "\"" }; case (#Array(v)) { var s = "["; for (i in v.vals()) { - if (s != "[") { s #= ", "; }; + if (s != "[") { s #= ", " }; s #= show(i); }; s # "]"; @@ -35,45 +40,55 @@ module JSON { case (#Object(v)) { var s = "{"; for ((k, v) in v.vals()) { - if (s != "{") { s #= ", "; }; + if (s != "{") { s #= ", " }; s #= "\"" # k # "\"" # ": " # show(v); }; s # "}"; }; case (#Boolean(v)) { - if (v) { return "true"; }; + if (v) { return "true" }; "false"; }; - case (#Null) { "null"; }; + case (#Null) { "null" }; }; private func character() : P.Parser = C.oneOf([ - C.sat(func (c : Char) : Bool { - c != Char.fromNat32(0x22) and c != '\\'; - }), + C.sat( + func(c : Char) : Bool { + c != Char.fromNat32(0x22) and c != '\\'; + }, + ), C.right( C.Character.char('\\'), C.map( C.Character.oneOf([ - Char.fromNat32(0x22), '\\', '/', 'b', 'f', 'n', 'r', 't', + Char.fromNat32(0x22), + '\\', + '/', + 'b', + 'f', + 'n', + 'r', + 't', // TODO: u hex{4} ]), - func (c : Char) : Char { + func(c : Char) : Char { switch (c) { - case ('b') { Char.fromNat32(0x08); }; - case ('f') { Char.fromNat32(0x0C); }; - case ('n') { Char.fromNat32(0x0A); }; - case ('r') { Char.fromNat32(0x0D); }; - case ('t') { Char.fromNat32(0x09); }; - case (_) { c; }; + case ('b') { Char.fromNat32(0x08) }; + case ('f') { Char.fromNat32(0x0C) }; + case ('n') { Char.fromNat32(0x0A) }; + case ('r') { Char.fromNat32(0x0D) }; + case ('t') { Char.fromNat32(0x09) }; + case (_) { c }; }; - } - ) - ) + }, + ), + ), ]); private func ignoreSpace(parserA : P.Parser) : P.Parser = C.right( - C.many(C.Character.space()), parserA + C.many(C.Character.space()), + parserA, ); public func parse(t : Text) : ?JSON = parseValues(L.fromText(t)); @@ -83,18 +98,20 @@ module JSON { public func parseRawASCII(data : [Nat8]) : ?JSON = parseValues(nat8sToCharList(data.vals())); private func parseValues(l : List.List) : ?JSON = switch (valueParser()(l)) { - case (null) { null; }; - case (? (x, xs)) { + case (null) { null }; + case (?(x, xs)) { switch (xs) { - case (null) { ?x; }; - case (_) { null; }; + case (null) { ?x }; + case (_) { null }; }; }; }; private func nat8sToCharList(i : Iter.Iter) : List.List = switch (i.next()) { - case (null) { null; }; - case (? v) { ?(Char.fromNat32(Nat32.fromNat(Nat8.toNat(v))), nat8sToCharList(i)); }; + case (null) { null }; + case (?v) { + ?(Char.fromNat32(Nat32.fromNat(Nat8.toNat(v))), nat8sToCharList(i)); + }; }; private func valueParser() : P.Parser = C.bracket( @@ -103,11 +120,12 @@ module JSON { objectParser(), arrayParser(), stringParser(), + floatParser(), numberParser(), boolParser(), - nullParser() + nullParser(), ]), - C.many(C.Character.space()) + C.many(C.Character.space()), ); private func objectParser() : P.Parser = C.map( @@ -118,18 +136,18 @@ module JSON { C.seq( C.left( ignoreSpace(string()), - ignoreSpace(C.Character.char(':')) + ignoreSpace(C.Character.char(':')), ), - P.delay(valueParser) + P.delay(valueParser), ), - C.Character.char(',') - ) + C.Character.char(','), + ), ), - C.Character.char('}') + C.Character.char('}'), ), - func (t : List.List<(Text, JSON)>) : JSON { + func(t : List.List<(Text, JSON)>) : JSON { #Object(List.toArray(t)); - } + }, ); private func arrayParser() : P.Parser = C.map( @@ -138,65 +156,105 @@ module JSON { ignoreSpace( C.sepBy( P.delay(valueParser), - C.Character.char(',') + C.Character.char(','), ), ), - C.Character.char(']') + C.Character.char(']'), ), - func (t : List.List) : JSON { + func(t : List.List) : JSON { #Array(List.toArray(t)); - } + }, ); private func string() : P.Parser = C.map( C.bracket( C.Character.char(Char.fromNat32(0x22)), C.many(character()), - C.Character.char(Char.fromNat32(0x22)) + C.Character.char(Char.fromNat32(0x22)), ), - func (t : List.List) : Text { + func(t : List.List) : Text { Text.fromIter(L.toIter(t)); - } + }, ); private func stringParser() : P.Parser = C.map( C.map( C.bracket( - C.Character.char(Char.fromNat32(0x22)), - C.many(character()), - C.Character.char(Char.fromNat32(0x22)) + C.Character.char(Char.fromNat32(0x22)), + C.many(character()), + C.Character.char(Char.fromNat32(0x22)), ), - func (t : List.List) : Text { + func(t : List.List) : Text { Text.fromIter(L.toIter(t)); }, ), - func (t : Text) : JSON { + func(t : Text) : JSON { #String(t); - } + }, + ); + + private func parseFloat() : P.Parser)> { + C.seq>( + C.Int.int(), + C.right( + C.Character.char('.'), + C.many1(C.Character.digit()), + ), + ); + }; + + private func listToNat(list : List) : Nat { + var n = 0; + + for (c in Iter.fromList(list)) { + n := n * 10 + Nat32.toNat(Char.toNat32(c) - Char.toNat32('0')); + }; + + n; + }; + + private func floatParser() : P.Parser = C.map( + parseFloat(), + func((n, decimal_list) : (Int, List)) : JSON { + let n_of_decimals = Float.fromInt(List.size(decimal_list)); + + let num = Float.fromInt(n); + let decimals = Float.fromInt(listToNat(decimal_list)) / (10 ** n_of_decimals); + + let isNegative = num < 0; + + let float = if (isNegative) { + num - decimals; + } else { + num + decimals; + }; + + #Float(float); + }, ); private func numberParser() : P.Parser = C.map( C.Int.int(), - func (i : Int) : JSON { + func(i : Int) : JSON { #Number(i); - } + }, ); private func boolParser() : P.Parser = C.map( C.choose( C.String.string("true"), - C.String.string("false") + C.String.string("false"), ), - func (t : Text) : JSON { + func(t : Text) : JSON { if (t == "true") return #Boolean(true); #Boolean(false); - } + }, ); private func nullParser() : P.Parser = C.map( C.String.string("null"), - func (_ : Text) : JSON { + func(_ : Text) : JSON { #Null; - } + }, ); }; diff --git a/test/Show.mo b/test/Show.mo index 9b06406..4fbde02 100644 --- a/test/Show.mo +++ b/test/Show.mo @@ -1,12 +1,17 @@ import JSON "../src/JSON"; import Text "mo:base-0.7.3/Text"; -assert(JSON.show(#String("hello")) == "\"hello\""); -assert(JSON.show(#Number(1)) == "1"); -assert(JSON.show(#Number(-1)) == "-1"); +assert (JSON.show(#String("hello")) == "\"hello\""); +assert (JSON.show(#Number(1)) == "1"); +assert (JSON.show(#Number(-1)) == "-1"); +assert (JSON.show(#Float(3.14)) == "3.14"); -assert(JSON.show(#Object([ - ("givenName", #String("John")), - ("familyName", #String("Doe")), - ("favNumber", #Number(5)) -])) == "{\"givenName\": \"John\", \"familyName\": \"Doe\", \"favNumber\": 5}"); +assert ( + JSON.show( + #Object([ + ("givenName", #String("John")), + ("familyName", #String("Doe")), + ("favNumber", #Number(5)), + ]), + ) == "{\"givenName\": \"John\", \"familyName\": \"Doe\", \"favNumber\": 5}", +); diff --git a/test/Value.mo b/test/Value.mo index 1b4e175..d536965 100644 --- a/test/Value.mo +++ b/test/Value.mo @@ -1,109 +1,121 @@ import JSON "../src/JSON"; switch (JSON.parse("{ }")) { - case (null) { assert(false); }; + case (null) { assert (false) }; case (?v) { switch (v) { case (#Object(v)) { - assert(v.size() == 0); + assert (v.size() == 0); }; - case (_) { assert(false); }; - } + case (_) { assert (false) }; + }; }; }; switch (JSON.parse("{ \"v\": 1 }")) { - case (null) { assert(false); }; + case (null) { assert (false) }; case (?v) { switch (v) { case (#Object(v)) { - assert(v.size() == 1); + assert (v.size() == 1); switch (v[0]) { case (("v", #Number(v))) { - assert(v == 1); + assert (v == 1); }; - case (_) { assert(false); }; + case (_) { assert (false) }; }; }; - case (_) { assert(false); }; - } + case (_) { assert (false) }; + }; }; }; switch (JSON.parse("[ ]")) { - case (null) { assert(false); }; + case (null) { assert (false) }; case (?v) { switch (v) { case (#Array(v)) { - assert(v.size() == 0); + assert (v.size() == 0); }; - case (_) { assert(false); }; - } + case (_) { assert (false) }; + }; }; }; switch (JSON.parse("[1, \"txt\"]")) { - case (null) { assert(false); }; + case (null) { assert (false) }; case (?v) { switch (v) { case (#Array(v)) { - assert(v.size() == 2); + assert (v.size() == 2); switch (v[0]) { - case (#Number(v)) { assert(v == 1); }; - case (_) { assert(false); }; + case (#Number(v)) { assert (v == 1) }; + case (_) { assert (false) }; }; switch (v[1]) { - case (#String(v)) { assert(v == "txt"); }; - case (_) { assert(false); }; + case (#String(v)) { assert (v == "txt") }; + case (_) { assert (false) }; }; }; - case (_) { assert(false); }; - } + case (_) { assert (false) }; + }; }; }; switch (JSON.parse("\"\\\"quoted\\\"\"")) { - case (null) { assert(false); }; + case (null) { assert (false) }; case (?v) { switch (v) { case (#String(v)) { - assert(v == "\"quoted\""); + assert (v == "\"quoted\""); }; - case (_) { assert(false); }; - } + case (_) { assert (false) }; + }; }; }; switch (JSON.parse("-100")) { - case (null) { assert(false); }; + case (null) { assert (false) }; case (?v) { switch (v) { case (#Number(v)) { - assert(v == -100); + assert (v == -100); }; - case (_) { assert(false); }; - } + case (_) { assert (false) }; + }; }; }; switch (JSON.parse("true")) { - case (null) { assert(false); }; + case (null) { assert (false) }; case (?v) { switch (v) { case (#Boolean(v)) { - assert(v == true); + assert (v == true); }; - case (_) { assert(false); }; - } + case (_) { assert (false) }; + }; }; }; -switch (JSON.parse(" null")) { // Test with spaces. - case (null) { assert(false); }; +switch (JSON.parse(" null")) { + // Test with spaces. + case (null) { assert (false) }; case (?v) { switch (v) { case (#Null) {}; - case (_) { assert(false); }; - } + case (_) { assert (false) }; + }; + }; +}; + +switch (JSON.parse(" 1.23")) { + // Test with spaces. + case (null) { assert (false) }; + case (?v) { + switch (v) { + case (#Float(1.23)) {}; + case (_) { assert (false) }; + }; }; }; From badd6010de0070f8e16b7b19c77e125fa5a7b375 Mon Sep 17 00:00:00 2001 From: Tomi Jaga Date: Sun, 18 Dec 2022 13:47:54 -0500 Subject: [PATCH 2/6] print float with 2 fixed decimal places --- src/JSON.mo | 138 +++++++++++++++++++++++--------------------------- test/Show.mo | 1 + test/Value.mo | 79 ++++++++++++++--------------- 3 files changed, 102 insertions(+), 116 deletions(-) diff --git a/src/JSON.mo b/src/JSON.mo index 9fffb26..0b9b313 100644 --- a/src/JSON.mo +++ b/src/JSON.mo @@ -1,8 +1,8 @@ import Char "mo:base-0.7.3/Char"; import Iter "mo:base-0.7.3/Iter"; import Int "mo:base-0.7.3/Int"; -import List "mo:base-0.7.3/List"; import Float "mo:base-0.7.3/Float"; +import List "mo:base-0.7.3/List"; import Nat8 "mo:base-0.7.3/Nat8"; import Nat32 "mo:base-0.7.3/Nat32"; import Result "mo:base-0.7.3/Result"; @@ -13,8 +13,6 @@ import L "mo:parser-combinators/List"; import P "mo:parser-combinators/Parser"; module JSON { - type List = List.List; - public type JSON = { #Number : Int; #Float : Float; @@ -26,13 +24,13 @@ module JSON { }; public func show(json : JSON) : Text = switch (json) { - case (#Number(v)) { Int.toText(v) }; - case (#Float(v)) { Float.toText(v) }; - case (#String(v)) { "\"" # v # "\"" }; + case (#Number(v)) { Int.toText(v); }; + case (#Float(v)) { Float.format(#fix 2, v); }; + case (#String(v)) { "\"" # v # "\""; }; case (#Array(v)) { var s = "["; for (i in v.vals()) { - if (s != "[") { s #= ", " }; + if (s != "[") { s #= ", "; }; s #= show(i); }; s # "]"; @@ -40,55 +38,45 @@ module JSON { case (#Object(v)) { var s = "{"; for ((k, v) in v.vals()) { - if (s != "{") { s #= ", " }; + if (s != "{") { s #= ", "; }; s #= "\"" # k # "\"" # ": " # show(v); }; s # "}"; }; case (#Boolean(v)) { - if (v) { return "true" }; + if (v) { return "true"; }; "false"; }; - case (#Null) { "null" }; + case (#Null) { "null"; }; }; private func character() : P.Parser = C.oneOf([ - C.sat( - func(c : Char) : Bool { - c != Char.fromNat32(0x22) and c != '\\'; - }, - ), + C.sat(func (c : Char) : Bool { + c != Char.fromNat32(0x22) and c != '\\'; + }), C.right( C.Character.char('\\'), C.map( C.Character.oneOf([ - Char.fromNat32(0x22), - '\\', - '/', - 'b', - 'f', - 'n', - 'r', - 't', + Char.fromNat32(0x22), '\\', '/', 'b', 'f', 'n', 'r', 't', // TODO: u hex{4} ]), - func(c : Char) : Char { + func (c : Char) : Char { switch (c) { - case ('b') { Char.fromNat32(0x08) }; - case ('f') { Char.fromNat32(0x0C) }; - case ('n') { Char.fromNat32(0x0A) }; - case ('r') { Char.fromNat32(0x0D) }; - case ('t') { Char.fromNat32(0x09) }; - case (_) { c }; + case ('b') { Char.fromNat32(0x08); }; + case ('f') { Char.fromNat32(0x0C); }; + case ('n') { Char.fromNat32(0x0A); }; + case ('r') { Char.fromNat32(0x0D); }; + case ('t') { Char.fromNat32(0x09); }; + case (_) { c; }; }; - }, - ), - ), + } + ) + ) ]); private func ignoreSpace(parserA : P.Parser) : P.Parser = C.right( - C.many(C.Character.space()), - parserA, + C.many(C.Character.space()), parserA ); public func parse(t : Text) : ?JSON = parseValues(L.fromText(t)); @@ -98,20 +86,18 @@ module JSON { public func parseRawASCII(data : [Nat8]) : ?JSON = parseValues(nat8sToCharList(data.vals())); private func parseValues(l : List.List) : ?JSON = switch (valueParser()(l)) { - case (null) { null }; - case (?(x, xs)) { + case (null) { null; }; + case (? (x, xs)) { switch (xs) { - case (null) { ?x }; - case (_) { null }; + case (null) { ?x; }; + case (_) { null; }; }; }; }; private func nat8sToCharList(i : Iter.Iter) : List.List = switch (i.next()) { - case (null) { null }; - case (?v) { - ?(Char.fromNat32(Nat32.fromNat(Nat8.toNat(v))), nat8sToCharList(i)); - }; + case (null) { null; }; + case (? v) { ?(Char.fromNat32(Nat32.fromNat(Nat8.toNat(v))), nat8sToCharList(i)); }; }; private func valueParser() : P.Parser = C.bracket( @@ -123,9 +109,9 @@ module JSON { floatParser(), numberParser(), boolParser(), - nullParser(), + nullParser() ]), - C.many(C.Character.space()), + C.many(C.Character.space()) ); private func objectParser() : P.Parser = C.map( @@ -136,18 +122,18 @@ module JSON { C.seq( C.left( ignoreSpace(string()), - ignoreSpace(C.Character.char(':')), + ignoreSpace(C.Character.char(':')) ), - P.delay(valueParser), + P.delay(valueParser) ), - C.Character.char(','), - ), + C.Character.char(',') + ) ), - C.Character.char('}'), + C.Character.char('}') ), - func(t : List.List<(Text, JSON)>) : JSON { + func (t : List.List<(Text, JSON)>) : JSON { #Object(List.toArray(t)); - }, + } ); private func arrayParser() : P.Parser = C.map( @@ -156,45 +142,45 @@ module JSON { ignoreSpace( C.sepBy( P.delay(valueParser), - C.Character.char(','), + C.Character.char(',') ), ), - C.Character.char(']'), + C.Character.char(']') ), - func(t : List.List) : JSON { + func (t : List.List) : JSON { #Array(List.toArray(t)); - }, + } ); private func string() : P.Parser = C.map( C.bracket( C.Character.char(Char.fromNat32(0x22)), C.many(character()), - C.Character.char(Char.fromNat32(0x22)), + C.Character.char(Char.fromNat32(0x22)) ), - func(t : List.List) : Text { + func (t : List.List) : Text { Text.fromIter(L.toIter(t)); - }, + } ); private func stringParser() : P.Parser = C.map( C.map( C.bracket( - C.Character.char(Char.fromNat32(0x22)), - C.many(character()), - C.Character.char(Char.fromNat32(0x22)), + C.Character.char(Char.fromNat32(0x22)), + C.many(character()), + C.Character.char(Char.fromNat32(0x22)) ), - func(t : List.List) : Text { + func (t : List.List) : Text { Text.fromIter(L.toIter(t)); }, ), - func(t : Text) : JSON { + func (t : Text) : JSON { #String(t); - }, + } ); - private func parseFloat() : P.Parser)> { - C.seq>( + private func parseFloat() : P.Parser)> { + C.seq>( C.Int.int(), C.right( C.Character.char('.'), @@ -203,7 +189,7 @@ module JSON { ); }; - private func listToNat(list : List) : Nat { + private func listToNat(list : List.List) : Nat { var n = 0; for (c in Iter.fromList(list)) { @@ -215,7 +201,7 @@ module JSON { private func floatParser() : P.Parser = C.map( parseFloat(), - func((n, decimal_list) : (Int, List)) : JSON { + func((n, decimal_list) : (Int, List.List)) : JSON { let n_of_decimals = Float.fromInt(List.size(decimal_list)); let num = Float.fromInt(n); @@ -235,26 +221,26 @@ module JSON { private func numberParser() : P.Parser = C.map( C.Int.int(), - func(i : Int) : JSON { + func (i : Int) : JSON { #Number(i); - }, + } ); private func boolParser() : P.Parser = C.map( C.choose( C.String.string("true"), - C.String.string("false"), + C.String.string("false") ), - func(t : Text) : JSON { + func (t : Text) : JSON { if (t == "true") return #Boolean(true); #Boolean(false); - }, + } ); private func nullParser() : P.Parser = C.map( C.String.string("null"), - func(_ : Text) : JSON { + func (_ : Text) : JSON { #Null; - }, + } ); }; diff --git a/test/Show.mo b/test/Show.mo index 4fbde02..6fdd709 100644 --- a/test/Show.mo +++ b/test/Show.mo @@ -1,5 +1,6 @@ import JSON "../src/JSON"; import Text "mo:base-0.7.3/Text"; +import Debug "mo:base-0.7.3/Debug"; assert (JSON.show(#String("hello")) == "\"hello\""); assert (JSON.show(#Number(1)) == "1"); diff --git a/test/Value.mo b/test/Value.mo index d536965..15ced2b 100644 --- a/test/Value.mo +++ b/test/Value.mo @@ -1,111 +1,110 @@ import JSON "../src/JSON"; switch (JSON.parse("{ }")) { - case (null) { assert (false) }; + case (null) { assert(false); }; case (?v) { switch (v) { case (#Object(v)) { - assert (v.size() == 0); + assert(v.size() == 0); }; - case (_) { assert (false) }; - }; + case (_) { assert(false); }; + } }; }; switch (JSON.parse("{ \"v\": 1 }")) { - case (null) { assert (false) }; + case (null) { assert(false); }; case (?v) { switch (v) { case (#Object(v)) { - assert (v.size() == 1); + assert(v.size() == 1); switch (v[0]) { case (("v", #Number(v))) { - assert (v == 1); + assert(v == 1); }; - case (_) { assert (false) }; + case (_) { assert(false); }; }; }; - case (_) { assert (false) }; - }; + case (_) { assert(false); }; + } }; }; switch (JSON.parse("[ ]")) { - case (null) { assert (false) }; + case (null) { assert(false); }; case (?v) { switch (v) { case (#Array(v)) { - assert (v.size() == 0); + assert(v.size() == 0); }; - case (_) { assert (false) }; - }; + case (_) { assert(false); }; + } }; }; switch (JSON.parse("[1, \"txt\"]")) { - case (null) { assert (false) }; + case (null) { assert(false); }; case (?v) { switch (v) { case (#Array(v)) { - assert (v.size() == 2); + assert(v.size() == 2); switch (v[0]) { - case (#Number(v)) { assert (v == 1) }; - case (_) { assert (false) }; + case (#Number(v)) { assert(v == 1); }; + case (_) { assert(false); }; }; switch (v[1]) { - case (#String(v)) { assert (v == "txt") }; - case (_) { assert (false) }; + case (#String(v)) { assert(v == "txt"); }; + case (_) { assert(false); }; }; }; - case (_) { assert (false) }; - }; + case (_) { assert(false); }; + } }; }; switch (JSON.parse("\"\\\"quoted\\\"\"")) { - case (null) { assert (false) }; + case (null) { assert(false); }; case (?v) { switch (v) { case (#String(v)) { - assert (v == "\"quoted\""); + assert(v == "\"quoted\""); }; - case (_) { assert (false) }; - }; + case (_) { assert(false); }; + } }; }; switch (JSON.parse("-100")) { - case (null) { assert (false) }; + case (null) { assert(false); }; case (?v) { switch (v) { case (#Number(v)) { - assert (v == -100); + assert(v == -100); }; - case (_) { assert (false) }; - }; + case (_) { assert(false); }; + } }; }; switch (JSON.parse("true")) { - case (null) { assert (false) }; + case (null) { assert(false); }; case (?v) { switch (v) { case (#Boolean(v)) { - assert (v == true); + assert(v == true); }; - case (_) { assert (false) }; - }; + case (_) { assert(false); }; + } }; }; -switch (JSON.parse(" null")) { - // Test with spaces. - case (null) { assert (false) }; +switch (JSON.parse(" null")) { // Test with spaces. + case (null) { assert(false); }; case (?v) { switch (v) { case (#Null) {}; - case (_) { assert (false) }; - }; + case (_) { assert(false); }; + } }; }; @@ -118,4 +117,4 @@ switch (JSON.parse(" 1.23")) { case (_) { assert (false) }; }; }; -}; +}; \ No newline at end of file From b4ec76ad2bc902c2db0532c1cb6cefda0a2913fa Mon Sep 17 00:00:00 2001 From: Tomi Jaga Date: Sun, 25 Dec 2022 13:53:30 -0500 Subject: [PATCH 3/6] parse floats with exponent and format float to the exact number of dps --- src/JSON.mo | 72 +++++++++++++++++++++++++++++++++++---------------- test/Show.mo | 6 ++++- test/Value.mo | 13 ++++++++++ 3 files changed, 67 insertions(+), 24 deletions(-) diff --git a/src/JSON.mo b/src/JSON.mo index 0b9b313..56aec97 100644 --- a/src/JSON.mo +++ b/src/JSON.mo @@ -25,7 +25,7 @@ module JSON { public func show(json : JSON) : Text = switch (json) { case (#Number(v)) { Int.toText(v); }; - case (#Float(v)) { Float.format(#fix 2, v); }; + case (#Float(v)) { Float.format(#exact, v); }; case (#String(v)) { "\"" # v # "\""; }; case (#Array(v)) { var s = "["; @@ -179,16 +179,6 @@ module JSON { } ); - private func parseFloat() : P.Parser)> { - C.seq>( - C.Int.int(), - C.right( - C.Character.char('.'), - C.many1(C.Character.digit()), - ), - ); - }; - private func listToNat(list : List.List) : Nat { var n = 0; @@ -200,22 +190,58 @@ module JSON { }; private func floatParser() : P.Parser = C.map( - parseFloat(), - func((n, decimal_list) : (Int, List.List)) : JSON { - let n_of_decimals = Float.fromInt(List.size(decimal_list)); + C.oneOf([ + parseFloatWithExp(), + parseFloat(), + ]), + func(n : Float) : JSON = #Float(n), + ); - let num = Float.fromInt(n); - let decimals = Float.fromInt(listToNat(decimal_list)) / (10 ** n_of_decimals); + private func parseFloat() : P.Parser { + C.map( + C.seq>( + C.Int.int(), + C.right( + C.Character.char('.'), + C.many1(C.Character.digit()), + ), + ), + func((n, decimal_list) : (Int, List.List)) : Float { + let n_of_decimals = Float.fromInt(List.size(decimal_list)); - let isNegative = num < 0; + let num = Float.fromInt(n); + let decimals = Float.fromInt(listToNat(decimal_list)) / (10 ** n_of_decimals); - let float = if (isNegative) { - num - decimals; - } else { - num + decimals; - }; + let isNegative = num < 0; + + let float = if (isNegative) { + num - decimals; + } else { + num + decimals; + }; + + float; + }, + ); + }; - #Float(float); + private func parseFloatWithExp() : P.Parser = C.map( + C.seq( + C.oneOf([ + parseFloat(), + C.map( + C.Int.int(), + func(i : Int) : Float = Float.fromInt(i), + ), + ]), + C.right( + C.oneOf([C.String.string("e"), C.String.string("E")]), + C.Int.int(), + ), + ), + func((n, exponent) : (Float, Int)) : Float { + let exp = Float.fromInt(exponent); + n * (10 ** exp); }, ); diff --git a/test/Show.mo b/test/Show.mo index 6fdd709..941b3db 100644 --- a/test/Show.mo +++ b/test/Show.mo @@ -5,7 +5,11 @@ import Debug "mo:base-0.7.3/Debug"; assert (JSON.show(#String("hello")) == "\"hello\""); assert (JSON.show(#Number(1)) == "1"); assert (JSON.show(#Number(-1)) == "-1"); -assert (JSON.show(#Float(3.14)) == "3.14"); +assert (JSON.show(#Number(-1)) == "-1"); + +assert (JSON.show(#Float(-3.14)) == "-3.1400000000000001"); +assert (JSON.show(#Float(1.234e-4)) == "0.00012339999999999999"); +assert (JSON.show(#Float(43e-02)) == "0.42999999999999999"); assert ( JSON.show( diff --git a/test/Value.mo b/test/Value.mo index 15ced2b..ff08fa9 100644 --- a/test/Value.mo +++ b/test/Value.mo @@ -1,3 +1,5 @@ +import Debug "mo:base-0.7.3/Debug"; + import JSON "../src/JSON"; switch (JSON.parse("{ }")) { @@ -117,4 +119,15 @@ switch (JSON.parse(" 1.23")) { case (_) { assert (false) }; }; }; +}; + +switch (JSON.parse(" 1.234e-4")) { + // Test with spaces. + case (null) { assert (false) }; + case (?v) { + switch (v) { + case (#Float(0.000_123_400_000_000_000_02)) {}; + case (_) { assert (false) }; + }; + }; }; \ No newline at end of file From 0efa889f8843435095734c702b8eb081342a11cb Mon Sep 17 00:00:00 2001 From: Tomi Jaga Date: Mon, 2 Jan 2023 12:59:39 -0500 Subject: [PATCH 4/6] revert Float text format to 2 dp --- src/JSON.mo | 41 +++++++++++++++++++++-------------------- test/Show.mo | 6 +++--- test/Value.mo | 4 +--- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/JSON.mo b/src/JSON.mo index 56aec97..94a806e 100644 --- a/src/JSON.mo +++ b/src/JSON.mo @@ -25,7 +25,7 @@ module JSON { public func show(json : JSON) : Text = switch (json) { case (#Number(v)) { Int.toText(v); }; - case (#Float(v)) { Float.format(#exact, v); }; + case (#Float(v)) { Float.format(#fix(2), v); }; case (#String(v)) { "\"" # v # "\""; }; case (#Array(v)) { var s = "["; @@ -179,16 +179,6 @@ module JSON { } ); - private func listToNat(list : List.List) : Nat { - var n = 0; - - for (c in Iter.fromList(list)) { - n := n * 10 + Nat32.toNat(Char.toNat32(c) - Char.toNat32('0')); - }; - - n; - }; - private func floatParser() : P.Parser = C.map( C.oneOf([ parseFloatWithExp(), @@ -207,20 +197,25 @@ module JSON { ), ), func((n, decimal_list) : (Int, List.List)) : Float { - let n_of_decimals = Float.fromInt(List.size(decimal_list)); + let isNegative = n < 0; + var num = n; + var n_of_decimals : Float = 0; - let num = Float.fromInt(n); - let decimals = Float.fromInt(listToNat(decimal_list)) / (10 ** n_of_decimals); + for (char in Iter.fromList(decimal_list)) { + let digit = Nat32.toNat( + Char.toNat32(char) - Char.toNat32('0') + ); - let isNegative = num < 0; + if (isNegative) { + num := num * 10 - digit; + } else { + num := num * 10 + digit; + }; - let float = if (isNegative) { - num - decimals; - } else { - num + decimals; + n_of_decimals += 1; }; - float; + let float = Float.fromInt(num) / (10 ** n_of_decimals); }, ); }; @@ -241,6 +236,12 @@ module JSON { ), func((n, exponent) : (Float, Int)) : Float { let exp = Float.fromInt(exponent); + let isNegative = exp < 0; + + if (isNegative) { + return n / (10 ** -exp); + }; + n * (10 ** exp); }, ); diff --git a/test/Show.mo b/test/Show.mo index 941b3db..b257eca 100644 --- a/test/Show.mo +++ b/test/Show.mo @@ -7,9 +7,9 @@ assert (JSON.show(#Number(1)) == "1"); assert (JSON.show(#Number(-1)) == "-1"); assert (JSON.show(#Number(-1)) == "-1"); -assert (JSON.show(#Float(-3.14)) == "-3.1400000000000001"); -assert (JSON.show(#Float(1.234e-4)) == "0.00012339999999999999"); -assert (JSON.show(#Float(43e-02)) == "0.42999999999999999"); +assert (JSON.show(#Float(-3.14)) == "-3.14"); +assert (JSON.show(#Float(1.234e-4)) == "0.00"); +assert (JSON.show(#Float(43e-02)) == "0.43"); assert ( JSON.show( diff --git a/test/Value.mo b/test/Value.mo index ff08fa9..a0a56b4 100644 --- a/test/Value.mo +++ b/test/Value.mo @@ -1,5 +1,3 @@ -import Debug "mo:base-0.7.3/Debug"; - import JSON "../src/JSON"; switch (JSON.parse("{ }")) { @@ -126,7 +124,7 @@ switch (JSON.parse(" 1.234e-4")) { case (null) { assert (false) }; case (?v) { switch (v) { - case (#Float(0.000_123_400_000_000_000_02)) {}; + case (#Float(1.234e-4)) {}; case (_) { assert (false) }; }; }; From 11c33b4512947673e279a4d26332584ad209dabb Mon Sep 17 00:00:00 2001 From: Tomi Jaga Date: Mon, 2 Jan 2023 13:36:30 -0500 Subject: [PATCH 5/6] rm duplicate test --- test/Show.mo | 1 - 1 file changed, 1 deletion(-) diff --git a/test/Show.mo b/test/Show.mo index b257eca..79ebe69 100644 --- a/test/Show.mo +++ b/test/Show.mo @@ -5,7 +5,6 @@ import Debug "mo:base-0.7.3/Debug"; assert (JSON.show(#String("hello")) == "\"hello\""); assert (JSON.show(#Number(1)) == "1"); assert (JSON.show(#Number(-1)) == "-1"); -assert (JSON.show(#Number(-1)) == "-1"); assert (JSON.show(#Float(-3.14)) == "-3.14"); assert (JSON.show(#Float(1.234e-4)) == "0.00"); From f3c8e7d418a7a8f2d6c0d7e2d276a0a82c2046ff Mon Sep 17 00:00:00 2001 From: Tomi Jaga Date: Tue, 7 Mar 2023 20:15:05 -0500 Subject: [PATCH 6/6] update readme to show limitations of the #Float type --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index da64cd7..09b4fd3 100644 --- a/README.md +++ b/README.md @@ -23,3 +23,13 @@ Debug.print(JSON.show(#Object([ ]))); // {"name": {"firstName": "quint"}, "username": "di-wu"} ``` + +> Note: The `#Float` type only formats to 2 decimal places. + +```motoko +import JSON "mo:json/JSON"; +import Debug "mo:base/Debug"; + +Debug.print(JSON.show(#Object([("amount", #Float(32.4829))]))); +// {"amount": "32.48"} +``` \ No newline at end of file