8000 refactor: handle any json number by peter-jerry-ye · Pull Request #2370 · moonbitlang/core · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

refactor: handle any json number #2370

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions builtin/builtin.mbti
Original file line number Diff line number Diff line change
Expand Up @@ -235,19 +235,19 @@ pub(all) enum IterResult {
}
impl Eq for IterResult

pub(all) enum Json {
pub enum Json {
Null
True
False
Number(Double)
Number(Double, repr~ : String?)
String(String)
Array(Array[Json])
Object(Map[String, Json])
}
fn Json::array(Array[Self]) -> Self
fn Json::boolean(Bool) -> Self
fn Json::null() -> Self
fn Json::number(Double) -> Self
fn Json::number(Double, repr? : String) -> Self
fn Json::object(Map[String, Self]) -> Self
fn Json::string(String) -> Self
impl Default for Json
Expand Down
41 changes: 29 additions & 12 deletions builtin/json.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,29 @@
// limitations under the License.

///|
#visibility(change_to="readonly", "Use helper functions like `Json::object(...)` instead")
pub(all) enum Json {
pub enum Json {
Null
True
False
Number(Double)
Number(Double, repr~ : String?) // 1.0000000000000000000e100
String(String)
Array(Array[Json])
Object(Map[String, Json])
} derive(Eq)
}

///|
pub impl Eq for Json with op_equal(a, b) {
match (a, b) {
(Null, Null) => true
(True, True) => true
(False, False) => true
(Number(a_num, ..), Number(b_num, ..)) => a_num == b_num
(String(a_str), String(b_str)) => a_str == b_str
(Array(a_arr), Array(b_arr)) => a_arr == b_arr
(Object(a_obj), Object(b_obj)) => a_obj == b_obj
_ => false
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

match a {
Null => b is Null
...
}

}

///|
/// Creates a JSON null value.
Expand Down Expand Up @@ -51,10 +64,14 @@ pub fn Json::null() -> Json {
/// Example:
///
/// ```moonbit
/// inspect(Json::number(3.14), content="Number(3.14)")
/// inspect(Json::number(3.14), content="Number(3.14)")
/// inspect(
/// Json::number(@double.infinity, repr="1e9999999999999999999999999999999").stringify(),
/// content="1e9999999999999999999999999999999"
/// )
/// ```
pub fn Json::number(number : Double) -> Json {
return Number(number)
pub fn Json::number(number : Double, repr? : String) -> Json {
return Number(number, repr~)
}

///|
Expand Down Expand Up @@ -160,12 +177,12 @@ pub impl ToJson for Bool with to_json(self : Bool) -> Json {

///|
pub impl ToJson for Byte with to_json(self : Byte) -> Json {
Number(self.to_double())
Json::number(self.to_double())
}

///|
pub impl ToJson for Int with to_json(self : Int) -> Json {
Number(self.to_double())
Json::number(self.to_double())
}

///|
Expand All @@ -175,7 +192,7 @@ pub impl ToJson for Int64 with to_json(self : Int64) -> Json {

///|
pub impl ToJson for UInt with to_json(self : UInt) -> Json {
Number(self.to_uint64().to_double())
Json::number(self.to_uint64().to_double())
}

///|
Expand All @@ -190,12 +207,12 @@ pub impl ToJson for Double with to_json(self : Double) -> Json {
self < 0xFFEFFFFFFFFFFFFFL.reinterpret_as_double() {
return Null
}
Number(self)
Json::number(self)
}

///|
pub impl ToJson for Float with to_json(self : Float) -> Json {
Number(self.to_double())
Json::number(self.to_double())
}

///|
Expand Down
12 changes: 9 additions & 3 deletions json/from_json.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ pub impl FromJson for Bool with from_json(json, path) {

///|
pub impl FromJson for Int with from_json(json, path) {
guard json is Number(n) else {
guard json is Number(n, ..) &&
n != @double.infinity &&
n != @double.neg_infinity else {
decode_error(path, "Int::from_json: expected number")
}
n.to_int()
Expand All @@ -65,7 +67,9 @@ pub impl FromJson for Int64 with from_json(json, path) {

///|
pub impl FromJson for UInt with from_json(json, path) {
guard json is Number(n) else {
guard json is Number(n, ..) &&
n != @double.infinity &&
n != @double.neg_infinity else {
decode_error(path, "UInt::from_json: expected number")
}
n.to_uint()
Expand All @@ -85,7 +89,9 @@ pub impl FromJson for UInt64 with from_json(json, path) {

///|
pub impl FromJson for Double with from_json(json, path) {
guard json is Number(n) else {
guard json is Number(n, ..) &&
n != @double.infinity &&
n != @double.neg_infinity else {
decode_error(path, "Double::from_json: expected number")
}
n
Expand Down
2 changes: 1 addition & 1 deletion json/internal_types.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ priv enum Token {
Null
True
False
Number(Double)
Number(Double, String?)
String(String)
LBrace
RBrace
Expand Down
8 changes: 6 additions & 2 deletions json/json.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub fn as_bool(self : JsonValue) -> Bool? {
///|
/// Try to get this element as a Number
pub fn as_number(self : JsonValue) -> Double? {
guard self is Number(n) else { return None }
guard self is Number(n, ..) else { return None }
Some(n)
}

Expand Down Expand Up @@ -146,7 +146,11 @@ pub fn stringify(
..write_char('\"')
.to_string()
}
Number(n) => n.to_string()
Number(n, repr~) =>
match repr {
None => n.to_string()
Some(r) => r
}
True => "true"
False => "false"
Null => "null"
Expand Down
4 changes: 2 additions & 2 deletions json/json_encode_decode_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,13 @@ fn of_json(jv : Json) -> AllThree raise DecodeError {
let strings_result = []
for n in ints {
match n {
Number(n) => ints_result.push(n.to_int())
Number(n, ..) => ints_result.push(n.to_int())
_ => () // error handling here
}
}
for n in floats {
match n {
Number(n) => floats_result.push(n)
Number(n, ..) => floats_result.push(n)
_ => () // error handling here
}
}
Expand Down
8 changes: 7 additions & 1 deletion json/json_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ test "stringify" {
// we do come across issues like ParseError not unified with String
let newjson = @json.parse(json.stringify())
match json {
{ "key": [_, _, _, _, { "value": Number(i), .. }, ..], .. } =>
{ "key": [_, _, _, _, { "value": Number(i, ..), .. }, ..], .. } =>
inspect(i, content="100")
_ => fail("Failed to match the JSON")
}
Expand Down Expand Up @@ -386,6 +386,12 @@ test "stringify number" {
for json in nums {
match (try? @json.parse(json.stringify())) {
Err(e) => err.push(e.to_string())
Ok(Number(_, repr~) as newjson) =>
if repr is Some(_) {
assert_eq(newjson.stringify(), json.stringify())
} else {
assert_eq(newjson, json)
}
Ok(newjson) => assert_eq(newjson, json)
}
}
Expand Down
16 changes: 8 additions & 8 deletions json/lex_main.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -62,25 +62,25 @@ fn ParseContext::lex_value(
Some('-') =>
match ctx.read_char() {
Some('0') => {
let n = ctx.lex_zero(start=ctx.offset - 2)
return Number(n)
let (n, repr) = ctx.lex_zero(start=ctx.offset - 2)
return Number(n, repr)
}
Some(c2) => {
if c2 is ('1'..='9') {
let n = ctx.lex_decimal_integer(start=ctx.offset - 2)
return Number(n)
let (n, repr) = ctx.lex_decimal_integer(start=ctx.offset - 2)
return Number(n, repr)
}
ctx.invalid_char(shift=-1)
}
None => raise InvalidEof
}
Some('0') => {
let n = ctx.lex_zero(start=ctx.offset - 1)
return Number(n)
let (n, repr) = ctx.lex_zero(start=ctx.offset - 1)
return Number(n, repr)
}
Some('1'..='9') => {
let n = ctx.lex_decimal_integer(start=ctx.offset - 1)
return Number(n)
let (n, repr) = ctx.lex_decimal_integer(start=ctx.offset - 1)
return Number(n, repr)
}
Some('"') => {
let s = ctx.lex_string()
Expand Down
45 changes: 35 additions & 10 deletions json/lex_number.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
fn ParseContext::lex_decimal_integer(
ctx : ParseContext,
start~ : Int
) -> Double raise ParseError {
) -> (Double, String?) raise ParseError {
for {
match ctx.read_char() {
Some('.') => return ctx.lex_decimal_point(start~)
Expand All @@ -37,7 +37,7 @@ fn ParseContext::lex_decimal_integer(
fn ParseContext::lex_decimal_point(
ctx : ParseContext,
start~ : Int
) -> Double raise ParseError {
) -> (Double, String?) raise ParseError {
match ctx.read_char() {
Some(c) =>
if c >= '0' && c <= '9' {
Expand All @@ -53,7 +53,7 @@ fn ParseContext::lex_decimal_point(
fn ParseContext::lex_decimal_fraction(
ctx : ParseContext,
start~ : Int
) -> Double raise ParseError {
) -> (Double, String?) raise ParseError {
for {
match ctx.read_char() {
Some('e' | 'E') => return ctx.lex_decimal_exponent(start~)
Expand All @@ -73,7 +73,7 @@ fn ParseContext::lex_decimal_fraction(
fn ParseContext::lex_decimal_exponent(
ctx : ParseContext,
start~ : Int
) -> Double raise ParseError {
) -> (Double, String?) raise ParseError {
match ctx.read_char() {
Some('+') | Some('-') => return ctx.lex_decimal_exponent_sign(start~)
Some(c) => {
Expand All @@ -91,7 +91,7 @@ fn ParseContext::lex_decimal_exponent(
fn ParseContext::lex_decimal_exponent_sign(
ctx : ParseContext,
start~ : Int
) -> Double raise ParseError {
) -> (Double, String?) raise ParseError {
match ctx.read_char() {
Some(c) => {
if c >= '0' && c <= '9' {
Expand All @@ -108,7 +108,7 @@ fn ParseContext::lex_decimal_exponent_sign(
fn ParseContext::lex_decimal_exponent_integer(
ctx : ParseContext,
start~ : Int
) -> Double raise ParseError {
) -> (Double, String?) {
for {
match ctx.read_char() {
Some(c) => {
Expand All @@ -127,7 +127,7 @@ fn ParseContext::lex_decimal_exponent_integer(
fn ParseContext::lex_zero(
ctx : ParseContext,
start~ : Int
) -> Double raise ParseError {
) -> (Double, String?) raise ParseError {
match ctx.read_char() {
Some('.') => ctx.lex_decimal_point(start~)
Some('e' | 'E') => ctx.lex_decimal_exponent(start~)
Expand All @@ -148,9 +148,34 @@ fn ParseContext::lex_number_end(
ctx : ParseContext,
start : Int,
end : Int
) -> Double raise ParseError {
) -> (Double, String?) {
let s = ctx.input.substring(start~, end~)
@strconv.parse_double(s) catch {
_ => raise InvalidNumber(offset_to_position(ctx.input, start), s)
if not(s.contains(".")) && not(s.contains("e")) && not(s.contains("E")) {
// If the string does not contain a decimal point or exponent, it is likely an integer
// We can try to parse it as an integer first
let parsed_int = try? @strconv.parse_int64(s)
match parsed_int {
Ok(i) if i <= 9007199254740991 && i >= -9007199254740991 =>
return (i.to_double(), None)
_ =>
return if s is ['-', ..] {
(@double.neg_infinity, Some(s))
} else {
(@double.infinity, Some(s))
}
}
} else {
let parsed_double = try? @strconv.parse_double(s)
match parsed_double {
// For normal values, return without string representation
Ok(d) => (d, None)
// If parsing fails as a double, treat it as infinity and preserve the string
Err(_) =>
if s is ['-', ..] {
(@double.neg_infinity, Some(s))
} else {
(@double.infinity, Some(s))
}
}
}
}
Loading
Loading
0