///|
priv enum CoreStep {
VisitCore(Json, Int32)
CoreArray(Int32)
CoreObject(Array[String])
}
///|
/// Capture the representation the caller's core Json still has.
/// Missing original numeric digits cannot be recovered.
pub fn from_core_json(
value : Json,
limits~ : Limits = Limits::new(),
) -> Result[Value, Diagnostic] {
let stack = [CoreStep::VisitCore(value, 0)]
let values : Array[Value] = []
let mut count = 0
let mut reserved = 1
let mut text_size = 0
while stack.pop() is Some(step) {
match step {
CoreArray(size) => {
let start = values.length() - size
let value = Value::array(Array::makei(size, i => values[start + i]))
values.truncate(start)
values.push(value)
}
CoreObject(keys) => {
let start = values.length() - keys.length()
let value = Value::object(
keys.mapi((i, key) => (key, values[start + i])),
).unwrap()
values.truncate(start)
values.push(value)
}
VisitCore(core, depth) => {
if count >= limits.max_nodes {
return Err(
Diagnostic::new(
LimitExceeded,
"Core JSON import node limit exceeded",
),
)
}
count += 1
let charge = match core {
Json::String(text) => text.length()
Json::Number(_, repr~) => repr.map(s => s.length()).unwrap_or(0)
_ => 1
}
if charge > limits.max_source - text_size {
return Err(
Diagnostic::new(
LimitExceeded,
"Core JSON import text limit exceeded",
),
)
}
text_size += charge
match core {
Json::Null => values.push(Value::null())
Json::True => values.push(Value::boolean(true))
Json::False => values.push(Value::boolean(false))
Json::String(text) => values.push(Value::string(text))
Json::Number(number, repr~) => {
let token = repr.unwrap_or_else(() => number.to_string())
if token.length() > limits.max_number {
return Err(
Diagnostic::new(
LimitExceeded,
"Core JSON import number token limit exceeded",
),
)
}
match Number::parse(token) {
Ok(number) => values.push(Value::number(number))
Err(d) => return Err(d)
}
}
Json::Array(items) => {
if items.length() > limits.max_nodes - reserved {
return Err(
Diagnostic::new(
LimitExceeded,
"Core JSON import node limit exceeded",
),
)
}
reserved += items.length()
if depth >= limits.max_depth {
return Err(
Diagnostic::new(
LimitExceeded,
"Core JSON import container limit exceeded",
),
)
}
stack.push(CoreArray(items.length()))
for i = items.length() - 1; i >= 0; i = i - 1 {
stack.push(VisitCore(items[i], depth + 1))
}
}
Json::Object(fields) => {
if fields.length() > limits.max_nodes - reserved {
return Err(
Diagnostic::new(
LimitExceeded,
"Core JSON import node limit exceeded",
),
)
}
reserved += fields.length()
if depth >= limits.max_depth {
return Err(
Diagnostic::new(
LimitExceeded,
"Core JSON import container limit exceeded",
),
)
}
let entries = fields.to_array()
for (key, _) in entries {
if key.length() > limits.max_source - text_size {
return Err(
Diagnostic::new(
LimitExceeded,
"Core JSON import key text limit exceeded",
),
)
}
text_size += key.length()
}
stack.push(CoreObject(entries.map(entry => entry.0)))
for i = entries.length() - 1; i >= 0; i = i - 1 {
stack.push(VisitCore(entries[i].1, depth + 1))
}
}
}
}
}
}
let value = values[0]
match write(value, limits~) {
Err(d) => Err(d)
Ok(_) => Ok(value)
}
}
///|
priv enum ExactStep {
VisitExact(Value)
ExactArray(Int32)
ExactObject(Array[String])
}
///|
/// Set core numeric repr explicitly. Core arithmetic/equality still use Double.
pub fn to_core_json(
value : Value,
limits~ : Limits = Limits::new(),
) -> Result[Json, Diagnostic] {
match write(value, limits~) {
Err(d) => return Err(d)
Ok(_) => ()
}
let stack = [ExactStep::VisitExact(value)]
let values : Array[Json] = []
while stack.pop() is Some(step) {
match step {
ExactArray(size) => {
let start = values.length() - size
let value = Json::array(Array::makei(size, i => values[start + i]))
values.truncate(start)
values.push(value)
}
ExactObject(keys) => {
let start = values.length() - keys.length()
let value = Json::object(
Map::from_array(keys.mapi((i, key) => (key, values[start + i]))),
)
values.truncate(start)
values.push(value)
}
VisitExact(exact) =>
match exact.kind {
Null => values.push(Json::null())
Boolean(b) => values.push(Json::boolean(b))
String(text) => values.push(Json::string(text))
Numeric(number) => {
let core = @core_json.parse(number.token[:]) catch {
_ =>
return Err(
Diagnostic::new(
InvalidNumber,
"Core JSON cannot represent this numeric token",
),
)
}
guard core is Json::Number(approximation, ..) else {
return Err(
Diagnostic::new(
InvalidNumber,
"Core JSON numeric conversion failed",
),
)
}
values.push(Json::number(approximation, repr=number.token))
}
Array(items) => {
stack.push(ExactArray(items.length()))
for i = items.length() - 1; i >= 0; i = i - 1 {
stack.push(VisitExact(items[i]))
}
}
Object(fields) => {
stack.push(ExactObject(fields.map(entry => entry.0)))
for i = fields.length() - 1; i >= 0; i = i - 1 {
stack.push(VisitExact(fields[i].1))
}
}
}
}
}
Ok(values[0])
}