// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// A borrowed reader's position, in this package's terms.
fn at_of(at : Int32, line : Int32, column : Int32) -> At {
{ at, line, column, }
}
///|
/// Carry a JSON value into the tree.
///
/// A number keeps the text the reader kept for it: `8080` becomes an integer and
/// `8080.5` a real, and a value written with an exponent that lands on a whole
/// number still reads as a real, because it was written as one.
///
/// An object's members come out sorted, because the JSON reader hands back a map
/// and a map has no order to preserve. The other formats in this repository keep
/// the order they were written in; this one cannot, and says so here rather than
/// pretending otherwise.
fn of_json_value(value : Json, source : String) -> Node {
let node = match value {
Json::Null => Node::new(Null, source, start)
Json::True => Node::new(Bool(true), source, start)
Json::False => Node::new(Bool(false), source, start)
Json::String(text) => Node::new(Text(text), source, start)
Json::Number(x, repr~) => {
let written = match repr {
Some(text) =>
text.contains(".") || text.contains("e") || text.contains("E")
None => x != x.trunc()
}
if written {
Node::new(Real(x), source, start)
} else {
match repr {
Some(text) =>
match whole(text[:]) {
Some(n) => Node::new(Int(n), source, start)
None => Node::new(Real(x), source, start)
}
None => Node::new(Int(x.to_int64()), source, start)
}
}
}
Json::Array(items) =>
Node::new(
List(items.map(fn(item) { of_json_value(item, source) })),
source,
start,
)
Json::Object(members) => {
let keys = members.keys().to_array()
keys.sort()
let table = Table::new()
for key in keys {
table.set(key, of_json_value(members[key], source))
}
Node::new(Map(table), source, start)
}
}
node
}
///|
/// Read a JSON document into the tree.
///
/// Positions are not carried: the reader this borrows reports where it refused,
/// not where each value sat, so a value carries the source name and the first
/// position. A refusal keeps the reader's own position.
pub fn of_json(text : StringView, source~ : String = "") -> Node raise Refused {
let value = @moonjson.loads(text, @moonjson.strict) catch {
refused =>
match refused.at() {
Some(at) =>
raise Format(
why=refused.to_repr().to_string(),
at=Some(at_of(at.at, at.line, at.column)),
)
None => raise Format(why=refused.to_repr().to_string(), at=None)
}
}
of_json_value(value, source)
}
///|
/// Read a YAML document into the tree.
///
/// One document, in YAML 1.2 core schema. Anchors, aliases and merge keys are
/// resolved by the reader; what arrives here is the value.
pub fn of_yaml(text : StringView, source~ : String = "") -> Node raise Refused {
let value = @moonyaml.loads(text) catch {
refused =>
raise Format(
why=refused.to_string(),
at=Some(at_of(refused.at().at, refused.at().line, refused.at().column)),
)
}
of_json_value(value, source)
}
///|
/// Read a TOML document into the tree.
pub fn of_toml(text : StringView, source~ : String = "") -> Node raise Refused {
let value = @moontoml.loads(text) catch {
refused =>
raise Format(
why=refused.to_string(),
at=Some(at_of(refused.at().at, refused.at().line, refused.at().column)),
)
}
of_json_value(value, source)
}