Skip to content
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

chore(stdlib): Cleanup json libary #2063

Merged
merged 7 commits into from
Mar 29, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
105 changes: 46 additions & 59 deletions stdlib/json.gr
Expand Up @@ -55,10 +55,25 @@ let _Float64_BOXED_VALUE_OFFSET = 8n
* ])
*/
provide enum rec Json {
/**
* Represents the JSON `null` value.
*/
JsonNull,
/**
* Represents a JSON boolean value.
*/
JsonBoolean(Bool),
/**
* Represents a JSON number value.
*/
JsonNumber(Number),
/**
* Represents a JSON string value.
*/
JsonString(String),
/**
* Represents a JSON array value.
*/
JsonArray(List<Json>),
// Note that JsonObject here is deliberately defined as a simple list of key value pair tuples as opposed
// to for example a Map in order to accommodate the fact that the ECMA-404 standard doesn't prohibit
Expand All @@ -67,6 +82,9 @@ provide enum rec Json {
// has the benefit of List's immutability. It's a conscious decision that sacrifices ease of use of the
// API for lossless handing of these edge cases with intention of later building more ergonomic APIs on a
// higher level of abstraction.
/**
* Represents a JSON object value, as a list of (key, value).
*/
JsonObject(List<(String, Json)>),
}

Expand Down Expand Up @@ -494,7 +512,6 @@ let emitEscapedUnicodeSequence = (codePoint: Number, buffer: Buffer.Buffer) => {
// High surrogate
let lowSurrogate = (uPrime & 0b00000000001111111111) + 0xDC00
// Low surrogate

emitUTF16EscapeSequence(highSurrogate, buffer)
emitUTF16EscapeSequence(lowSurrogate, buffer)
}
Expand Down Expand Up @@ -558,21 +575,9 @@ let printNumberWasmI64 = (value: WasmI64, buffer: Buffer.Buffer) => {
Buffer.addString(s, buffer)
}

@unsafe
let isFinite = (value: WasmF64) => {
use WasmF64.{ (==), (-) }
value - value == 0.0W
}

@unsafe
let isNaN = (value: WasmF64) => {
use WasmF64.{ (!=) }
value != value
}

@unsafe
let printNumberWasmF64 = (value: WasmF64, buffer: Buffer.Buffer) => {
if (isFinite(value)) {
if (NumberUtils.isFinite(value)) {
let s = NumberUtils.dtoa(value)
Buffer.addString(s, buffer)
None
Expand All @@ -585,7 +590,7 @@ let printNumberWasmF64 = (value: WasmF64, buffer: Buffer.Buffer) => {
// directly. Other possible choices were to throw exceptions or to
// continue formatting without representing these values correctly
// (like JavaScript's JSON.stringify).
if (isNaN(value)) {
if (NumberUtils.isNaN(value)) {
Some(InvalidNumber("NaN is not allowed in JsonNumber"))
} else if (value < 0.0W) {
Some(InvalidNumber("-Infinity is not allowed in JsonNumber"))
Expand Down Expand Up @@ -1558,20 +1563,22 @@ and parseString = (parserState: JsonParserState) => {
// '"'
Some(e) => return Err(e),
None => {
let mut done = false
let buffer = parserState.bufferParse
Buffer.clear(buffer)

while (!done) {
while (true) {
match (parserState.currentCodePoint) {
0x22 => { // '"'
next(parserState)
done = true
break
},
-1 => {
// just end the loop without setting done to true
break
-1 => { // EOF
return Err(
buildUnexpectedTokenError(
parserState,
"unexpected end of string value"
),
)
},
0x5C => { // '\'
// Keep the starting position for better error reporting.
Expand Down Expand Up @@ -1757,17 +1764,8 @@ and parseString = (parserState: JsonParserState) => {
}
}

if (done) {
let s = Buffer.toString(buffer)
return Ok(s)
} else {
return Err(
buildUnexpectedTokenError(
parserState,
"unexpected end of string value"
),
)
}
let s = Buffer.toString(buffer)
return Ok(s)
},
}
}
Expand Down Expand Up @@ -1887,10 +1885,14 @@ and parseNumberValue = (parserState: JsonParserState) => {
}
},
}
if (result == 0 && isNegative)
return Ok(JsonNumber(-0.0))
else
return Ok(JsonNumber(if (isNegative) result * -1 else result))

let result = if (result == 0 && isNegative) {
-0.0
} else {
if (isNegative) result * -1 else result
}

return Ok(JsonNumber(result))
}
and parseArray = (parserState: JsonParserState) => {
match (expectCodePointAndAdvance(0x5B, parserState)) {
Expand All @@ -1901,10 +1903,9 @@ and parseArray = (parserState: JsonParserState) => {

let mut elems = []: List<Json>

let mut done = false
let mut first = true
let mut trailingComma = false
while (!done) {
while (true) {
let c = parserState.currentCodePoint
match (c) {
0x2C => { // ','
Expand All @@ -1922,12 +1923,12 @@ and parseArray = (parserState: JsonParserState) => {
},
0x5D => { // ']'
next(parserState)
done = true
break
},
-1 => {
// just end the loop without setting done to true
break
-1 => { // EOF
return Err(
buildUnexpectedTokenError(parserState, "unexpected end of array"),
)
},
_ => {
// note that parseValue skips initial and final whitespace
Expand All @@ -1947,12 +1948,8 @@ and parseArray = (parserState: JsonParserState) => {
return Err(
buildUnexpectedTokenError(parserState, "unexpected end of array"),
)
} else if (done) {
return Ok(JsonArray(List.reverse(elems)))
} else {
return Err(
buildUnexpectedTokenError(parserState, "unexpected end of array"),
)
return Ok(JsonArray(List.reverse(elems)))
}
},
}
Expand All @@ -1964,12 +1961,11 @@ and parseObject = (parserState: JsonParserState) => {
None => {
let mut entries = []: List<(String, Json)>

let mut done = false
let mut first = true

// one iteration of this loop should correspond to a key-value pair
let mut trailingComma = false
while (!done) {
while (true) {
skipWhiteSpace(parserState)

let c = parserState.currentCodePoint
Expand All @@ -1994,7 +1990,6 @@ and parseObject = (parserState: JsonParserState) => {
return Err(buildUnexpectedTokenError(parserState, detail))
}
next(parserState)
done = true
break
},
_ => {
Expand Down Expand Up @@ -2027,15 +2022,7 @@ and parseObject = (parserState: JsonParserState) => {
}
// end of entry loop

if (done) {
return Ok(JsonObject(List.reverse(entries)))
} else {
// This branch is not expected to actually execute,
// but in case it does, may just as well do the right thing.
return Err(
buildUnexpectedTokenError(parserState, "unexpected end of object"),
)
}
return Ok(JsonObject(List.reverse(entries)))
},
}
}
Expand Down
38 changes: 38 additions & 0 deletions stdlib/json.md
Expand Up @@ -40,6 +40,44 @@ enum Json {

Data structure representing JSON in Grain.

Variants:

```grain
JsonNull
```

Represents the JSON `null` value.

```grain
JsonBoolean(Bool)
```

Represents a JSON boolean value.

```grain
JsonNumber(Number)
```

Represents a JSON number value.

```grain
JsonString(String)
```

Represents a JSON string value.

```grain
JsonArray(List<Json>)
```

Represents a JSON array value.

```grain
JsonObject(List<(String, Json)>)
```

Represents a JSON object value, as a list of (key, value).

Examples:

```grain
Expand Down
4 changes: 2 additions & 2 deletions stdlib/runtime/numberUtils.gr
Expand Up @@ -1452,13 +1452,13 @@ let get_dtoa_buf = () => {
}

@unsafe
let isFinite = value => {
provide let isFinite = value => {
use WasmF64.{ (==), (-) }
value - value == 0.0W
}

@unsafe
let isNaN = value => {
provide let isNaN = value => {
use WasmF64.{ (!=) }
value != value
}
Expand Down
12 changes: 12 additions & 0 deletions stdlib/runtime/numberUtils.md
Expand Up @@ -60,6 +60,18 @@ utoa64 : (value: WasmI64, radix: WasmI32) => String
itoa64 : (value: WasmI64, radix: WasmI32) => String
```

### NumberUtils.**isFinite**

```grain
isFinite : (value: WasmF64) => Bool
```

### NumberUtils.**isNaN**

```grain
isNaN : (value: WasmF64) => Bool
```

### NumberUtils.**dtoa**

```grain
Expand Down