///|
/// Input offsets use UTF-16 code units, matching MoonBit string locations.
pub(all) suberror ValueInputError {
InvalidJson(Int32, String)
DuplicateJsonKey(Int32)
JsonInputLimit(Int32, String)
} derive(Debug)
///|
priv struct JsonContainer {
object_ : Bool
keys : Set[String]
mut expect_key : Bool
}
///|
/// Parse one bounded JSON value, rejecting duplicate decoded object names.
pub fn parse_json_value(text : String) -> Json raise ValueInputError {
let mut bytes = 0
for c in text.iter() {
let n = c.to_int()
bytes += if n < 0x80 {
1
} else if n < 0x800 {
2
} else if n < 0x10000 {
3
} else {
4
}
if bytes > 16777216 {
raise JsonInputLimit(0, "I expected at most 16 MiB of UTF-8 JSON input.")
}
}
let stack : Array[JsonContainer] = []
let mut i = 0
while i < text.length() {
let c = text.code_unit_at(i).to_int()
match c {
34 => {
let start = i
i += 1
let mut closed = false
while i < text.length() {
let part = text.code_unit_at(i).to_int()
if part == 34 {
i += 1
closed = true
break
}
if part == 92 {
i += 2
} else {
i += 1
}
}
guard closed else {
raise InvalidJson(
start, "I expected a closing quote for the JSON string.",
)
}
if stack.last() is Some(frame) && frame.object_ && frame.expect_key {
let value = @json.parse(text[start:i].to_owned()) catch {
_ =>
raise InvalidJson(
start, "I expected a valid JSON object member name.",
)
}
guard value is String(name) else {
raise InvalidJson(start, "I expected a string object member name.")
}
if frame.keys.contains(name) {
raise DuplicateJsonKey(start)
}
frame.keys.add(name)
frame.expect_key = false
}
continue
}
123 | 91 => {
if stack.length() >= 128 {
raise JsonInputLimit(
i, "I expected JSON nesting depth no greater than 128.",
)
}
stack.push({ object_: c == 123, keys: Set([]), expect_key: c == 123, })
}
125 | 93 => {
guard stack.pop() is Some(frame) && frame.object_ == (c == 125) else {
raise InvalidJson(
i, "I expected a matching JSON container delimiter.",
)
}
}
44 =>
if stack.last() is Some(frame) && frame.object_ {
frame.expect_key = true
}
_ => ()
}
i += 1
}
@syntax.parse_json_value(text) catch {
_ => raise InvalidJson(i, "I expected one well-formed JSON value.")
}
}