use std::fmt::Write as _;
use std::io::Write;
use crate::tracer::{StepState, Tracer};
use crate::value::RegVal;
#[derive(Debug)]
pub struct JsonTracer<W: Write> {
out: W,
}
impl<W: Write> JsonTracer<W> {
pub fn new(out: W) -> Self {
Self { out }
}
}
fn json_escape(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for ch in s.chars() {
match ch {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if c.is_control() => {
let _ = write!(out, "\\u{:04x}", c as u32);
}
c => out.push(c),
}
}
out
}
fn regval_json(val: &RegVal) -> String {
match val {
RegVal::Unset => "{\"type\":\"unset\"}".to_string(),
RegVal::Int(v) => {
format!("{{\"type\":\"int\",\"value\":{v}}}")
}
RegVal::VecInt(v) => {
format!("{{\"type\":\"vec<int>\",\"len\":{}}}", v.len())
}
RegVal::VecXqmx(v) => {
format!("{{\"type\":\"vec<model>\",\"len\":{}}}", v.len())
}
RegVal::Model(m) => {
format!(
"{{\"type\":\"model\",\"rows\":{},\"cols\":{}}}",
m.rows, m.cols
)
}
RegVal::Sample(s) => {
format!("{{\"type\":\"sample\",\"len\":{}}}", s.values.len())
}
}
}
fn regs_json(regs: &[(u8, RegVal)]) -> String {
let mut out = String::from("{");
for (i, (idx, val)) in regs.iter().enumerate() {
if i > 0 {
out.push(',');
}
let _ = write!(out, "\"{}\":{}", idx, regval_json(val));
}
out.push('}');
out
}
fn stack_json(stack: &[i64]) -> String {
let mut out = String::from("[");
for (i, v) in stack.iter().enumerate() {
if i > 0 {
out.push(',');
}
let _ = write!(out, "{v}");
}
out.push(']');
out
}
impl<W: Write> Tracer for JsonTracer<W> {
type Error = std::io::Error;
fn on_step(&mut self, state: &StepState<'_>) -> Result<(), Self::Error> {
let instr = json_escape(&format!("{}", state.instruction));
writeln!(
self.out,
"{{\"step\":{},\"pos\":{},\"instruction\":\"{}\",\"stack\":{},\"read_regs\":{},\"written_regs\":{}}}",
state.step,
state.pos,
instr,
stack_json(state.stack),
regs_json(state.read_regs),
regs_json(state.written_regs),
)?;
Ok(())
}
}