///|
priv enum WriteFrame {
Array(Array[Json], mut i~ : Int) // (arr, index)
Object(Iter[(String, Json)], mut first~ : Bool) // (kvs, first)
}
///|
fn indent_string(level : Int, indent : Int) -> String {
if indent == 0 {
""
} else {
let spaces = indent * level
match spaces {
0 => "\n"
1 => "\n "
2 => "\n "
3 => "\n "
4 => "\n "
5 => "\n "
6 => "\n "
7 => "\n "
8 => "\n "
_ => "\n" + " ".repeat(spaces)
}
}
}
///|
fn escape(str : String, escape_slash~ : Bool) -> String {
let buf = StringBuilder::new(size_hint=str.length())
for c in str {
match c {
'"' => buf.write_string("\\\"")
'\\' => buf.write_string("\\\\")
'/' =>
if escape_slash {
buf.write_string("\\/")
} else {
buf.write_char(c)
}
'\n' => buf.write_string("\\n")
'\r' => buf.write_string("\\r")
'\b' => buf.write_string("\\b")
'\t' => buf.write_string("\\t")
_ => {
let code = c.to_int()
if code == 0x0C {
buf.write_string("\\f")
} else if code < ' ' {
buf.write_string("\\u00")
buf.write_string(code.to_byte().to_hex())
} else {
buf.write_char(c)
}
}
}
}
buf.to_string()
}
///|
pub fn Json::stringify(
self : Json,
escape_slash? : Bool = false,
indent? : Int = 0,
) -> String {
let buf = StringBuilder::new(size_hint=0)
// Explicit stack to replace recursive calls
let stack : Array[WriteFrame] = []
let mut depth = 0
loop Some(self) {
Some(value) => {
match value {
Object(members) =>
if members.is_empty() {
buf.write_string("{}")
} else {
depth += 1
buf.write_char('{')
buf.write_string(indent_string(depth, indent))
// After child value printed, we resume from this frame
stack.push(WriteFrame::Object(members.iter(), first=true))
}
Array(arr) =>
if arr.is_empty() {
buf.write_string("[]")
} else {
depth += 1
buf.write_char('[')
buf.write_string(indent_string(depth, indent))
stack.push(WriteFrame::Array(arr, i=0))
}
String(s) =>
buf
..write_char('\"')
..write_string(escape(s, escape_slash~))
..write_char('\"')
Number(n) => buf.write_string(n.0)
Bool(true) => buf.write_string("true")
Bool(false) => buf.write_string("false")
Null => buf.write_string("null")
}
continue None
}
None =>
// No current node to write; try to resume a pending container
match stack {
[] => break
[.., WriteFrame::Array(arr, i~) as frame] =>
if i < arr.length() {
let element = arr[i]
frame.i = i + 1
if i > 0 {
buf.write_char(',')
buf.write_string(indent_string(depth, indent))
}
continue Some(element)
} else {
depth -= 1
ignore(stack.pop())
buf.write_string(indent_string(depth, indent))
buf.write_char(']')
continue None
}
[.., WriteFrame::Object(iterator, first~) as frame] =>
match iterator.next() {
Some((k, v)) => {
if !first {
buf.write_char(',')
buf.write_string(indent_string(depth, indent))
}
buf
..write_char('\"')
..write_string(escape(k, escape_slash~))
..write_char('\"')
..write_char(':')
if indent > 0 {
buf.write_char(' ')
}
frame.first = false
continue Some(v)
}
None => {
depth -= 1
ignore(stack.pop())
buf.write_string(indent_string(depth, indent))
buf.write_char('}')
continue None
}
}
}
}
buf.to_string()
}