///|
priv enum ValueFrame {
Object(Map[String, Json], mut key~ : String?)
Array(Array[Json])
}
///|
/// Core JSON validates the grammar, Unicode and nesting first. This second,
/// iterative token pass rebuilds values with every number's original spelling.
/// Core currently retains large integers but rounds ordinary decimal tokens.
/// Rebuilding containers also preserves last-key-wins behavior for duplicate
/// keys, including duplicates whose earlier values have different structures.
fn parse_exact(text : String) -> Json raise @json.ParseError {
@json.parse(text) |> ignore
let stack : Array[ValueFrame] = []
let mut root = Json::null()
fn attach(value : Json) -> Unit {
match stack.last() {
Some(Object(fields, key~)) => if key is Some(key) { fields[key] = value }
Some(Array(values)) => values.push(value)
None => root = value
}
if stack.last() is Some(Object(_, ..) as frame) {
frame.key = None
}
}
let mut at = 0
while at < text.length() {
let start = at
match text.code_unit_at(at).to_int().unsafe_to_char() {
'{' => {
let fields : Map[String, Json] = Map([])
attach(Json::object(fields))
stack.push(Object(fields, key=None))
at += 1
}
'[' => {
let values : Array[Json] = []
attach(Json::array(values))
stack.push(Array(values))
at += 1
}
'}' | ']' => {
stack.pop() |> ignore
at += 1
}
'"' => {
at += 1
while at < text.length() {
let c = text.code_unit_at(at).to_int().unsafe_to_char()
at += 1
if c == '"' {
break
}
if c == '\\' {
at += 1
}
}
let value = @json.parse(text.unsafe_substring(start~, end=at))
match (stack.last(), value) {
(Some(Object(_, key=None) as frame), String(key)) =>
frame.key = Some(key)
_ => attach(value)
}
}
'-' | '0'..<='9' => {
at += 1
while at < text.length() {
let c = text.code_unit_at(at).to_int().unsafe_to_char()
if (c >= '0' && c <= '9') ||
c == '.' ||
c == 'e' ||
c == 'E' ||
c == '-' ||
c == '+' {
at += 1
} else {
break
}
}
let repr = text.unsafe_substring(start~, end=at)
// The whole document is valid, so this token is necessarily a number.
if @json.parse(repr) is Number(number, ..) {
attach(Json::number(number, repr~))
}
}
't' => {
attach(Json::boolean(true))
at += 4
}
'f' => {
attach(Json::boolean(false))
at += 5
}
'n' => {
attach(Json::null())
at += 4
}
_ => at += 1 // Validated whitespace, colon or comma.
}
}
root
}