use crate::CompiledNode;
use crate::OpCode;
use crate::node::PathSegment;
pub(crate) fn push_json_str(out: &mut String, s: &str) {
const HEX: &[u8; 16] = b"0123456789abcdef";
out.reserve(s.len() + 2);
out.push('"');
let mut run = 0;
for (i, b) in s.bytes().enumerate() {
let escape = match b {
b'"' => "\\\"",
b'\\' => "\\\\",
b'\n' => "\\n",
b'\r' => "\\r",
b'\t' => "\\t",
0x08 => "\\b",
0x0C => "\\f",
0x00..=0x1F => "",
_ => continue,
};
out.push_str(&s[run..i]);
if escape.is_empty() {
out.push_str("\\u00");
out.push(HEX[(b >> 4) as usize] as char);
out.push(HEX[(b & 0x0F) as usize] as char);
} else {
out.push_str(escape);
}
run = i + 1;
}
out.push_str(&s[run..]);
out.push('"');
}
#[inline]
fn segment_str(seg: &PathSegment) -> &str {
match seg {
PathSegment::Field(s) | PathSegment::FieldOrIndex(s, _) => s,
}
}
fn write_segments_array(out: &mut String, segments: &[PathSegment]) {
out.push('[');
for (i, s) in segments.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
push_json_str(out, segment_str(s));
}
out.push(']');
}
#[inline]
fn begin_call(out: &mut String, name: &str) {
out.push('{');
push_json_str(out, name);
out.push_str(": ");
}
pub(crate) fn node_to_json_string(node: &CompiledNode) -> String {
let mut out = String::new();
write_node_json(&mut out, node);
out
}
fn write_node_json(out: &mut String, node: &CompiledNode) {
match node {
CompiledNode::Value { value, .. } => out.push_str(&value.to_json_string()),
CompiledNode::Array { nodes, .. } => {
out.push('[');
for (i, n) in nodes.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
write_node_json(out, n);
}
out.push(']');
}
CompiledNode::BuiltinOperator { opcode, args, .. } => {
write_builtin_json(out, opcode, args);
}
CompiledNode::CustomOperator(data) => {
write_custom_json(out, &data.name, &data.args);
}
CompiledNode::Cse(data) => write_node_json(out, &data.inner),
#[cfg(feature = "templating")]
CompiledNode::StructuredObject(data) => write_structured_json(out, &data.fields),
CompiledNode::Var {
scope_level,
segments,
default_value,
..
} => write_compiled_var_json(out, *scope_level, segments, default_value.as_deref()),
#[cfg(feature = "ext-control")]
CompiledNode::Exists(data) => write_compiled_exists_json(out, &data.segments),
#[cfg(feature = "error-handling")]
CompiledNode::Throw(data) => {
begin_call(out, "throw");
if let datavalue::OwnedDataValue::Object(pairs) = &data.error
&& let Some((_, datavalue::OwnedDataValue::String(s))) =
pairs.iter().find(|(k, _)| k == "type")
{
push_json_str(out, s);
} else {
out.push_str(&data.error.to_json_string());
}
out.push('}');
}
CompiledNode::Missing(data) => {
out.push_str("{\"missing\": [");
for (i, a) in data.args.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
match a {
crate::node::CompiledMissingArg::Now((path, _)) => push_json_str(out, path),
crate::node::CompiledMissingArg::Later(n) => write_node_json(out, n),
}
}
out.push_str("]}");
}
CompiledNode::MissingSome(data) => {
out.push_str("{\"missing_some\": [");
match &data.min_present {
crate::node::CompiledMissingMin::Now(n) => {
let mut buf = itoa::Buffer::new();
out.push_str(buf.format(*n));
}
crate::node::CompiledMissingMin::Later(n) => write_node_json(out, n),
}
out.push_str(", ");
match &data.paths {
crate::node::CompiledMissingPaths::Now(paths) => {
out.push('[');
for (i, (p, _)) in paths.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
push_json_str(out, p);
}
out.push(']');
}
crate::node::CompiledMissingPaths::Later(n) => write_node_json(out, n),
}
out.push_str("]}");
}
CompiledNode::InvalidArgs { op_name, args, .. } => {
begin_call(out, op_name);
out.push_str(&args.to_json_string());
out.push('}');
}
}
}
fn write_args_json(out: &mut String, args: &[CompiledNode], inline: bool) {
if let [arg] = args {
if inline {
let start = out.len();
write_node_json(out, arg);
if out.as_bytes().get(start) == Some(&b'[') {
out.insert(start, '[');
out.push(']');
}
} else {
out.push('[');
write_node_json(out, arg);
out.push(']');
}
return;
}
out.push('[');
for (i, arg) in args.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
write_node_json(out, arg);
}
out.push(']');
}
fn write_builtin_json(out: &mut String, opcode: &OpCode, args: &[CompiledNode]) {
let inline = opcode.meta().args_form != crate::operators::meta::ArgsForm::ArrayOnly;
begin_call(out, opcode.as_str());
write_args_json(out, args, inline);
out.push('}');
}
#[cfg(feature = "trace")]
pub(crate) fn builtin_to_json_string(opcode: &OpCode, args: &[CompiledNode]) -> String {
let mut out = String::new();
write_builtin_json(&mut out, opcode, args);
out
}
fn write_custom_json(out: &mut String, name: &str, args: &[CompiledNode]) {
begin_call(out, name);
write_args_json(out, args, true);
out.push('}');
}
#[cfg(feature = "trace")]
pub(crate) fn custom_to_json_string(name: &str, args: &[CompiledNode]) -> String {
let mut out = String::new();
write_custom_json(&mut out, name, args);
out
}
#[cfg(feature = "templating")]
fn write_structured_json(out: &mut String, fields: &[(String, CompiledNode)]) {
out.push('{');
for (i, (key, node)) in fields.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
push_json_str(out, key);
out.push_str(": ");
write_node_json(out, node);
}
out.push('}');
}
#[cfg(all(feature = "templating", feature = "trace"))]
pub(crate) fn structured_to_json_string(fields: &[(String, CompiledNode)]) -> String {
let mut out = String::new();
write_structured_json(&mut out, fields);
out
}
fn write_compiled_var_json(
out: &mut String,
scope_level: u32,
segments: &[PathSegment],
default_value: Option<&CompiledNode>,
) {
if scope_level > 0 {
begin_call(out, "val");
out.push_str("[[");
let mut buf = itoa::Buffer::new();
out.push_str(buf.format(scope_level));
out.push(']');
for seg in segments {
out.push_str(", ");
push_json_str(out, segment_str(seg));
}
out.push_str("]}");
return;
}
let needs_val = match segments {
[seg] => segment_str(seg).is_empty() || segment_str(seg).contains('.'),
_ => segments.iter().any(|s| segment_str(s).contains('.')),
};
if needs_val && default_value.is_none() {
begin_call(out, "val");
match segments {
[seg] => push_json_str(out, segment_str(seg)),
_ => write_segments_array(out, segments),
}
out.push('}');
return;
}
let path = segments
.iter()
.map(segment_str)
.collect::<Vec<_>>()
.join(".");
begin_call(out, "var");
match default_value {
Some(def) => {
out.push('[');
push_json_str(out, &path);
out.push_str(", ");
write_node_json(out, def);
out.push(']');
}
None => push_json_str(out, &path),
}
out.push('}');
}
#[cfg(feature = "ext-control")]
fn write_compiled_exists_json(out: &mut String, segments: &[PathSegment]) {
begin_call(out, "exists");
match segments {
[seg] => push_json_str(out, segment_str(seg)),
_ => write_segments_array(out, segments),
}
out.push('}');
}
#[cfg(all(feature = "ext-control", feature = "trace"))]
pub(crate) fn compiled_exists_to_json_string(segments: &[PathSegment]) -> String {
let mut out = String::new();
write_compiled_exists_json(&mut out, segments);
out
}