1use sim_codec::{
11 CodecPrism, Input, Output, PrismDiagnostic, PrismOutput, RuntimeCodecPrism, decode_with_codec,
12 encode_with_codec,
13};
14use sim_kernel::{Cx, EncodeOptions, EncodePosition, Expr, ReadPolicy, Symbol};
15use sim_lib_scene::{data_map, node, sym};
16
17pub const MULTI_CODEC_LENS: &str = "view:codec-multi";
19
20pub const SYSEX_COMPARISON_LENS: &str = "view:codec-sysex-comparison";
22
23#[derive(Clone, Debug, PartialEq, Eq)]
25pub struct ProbeResult {
26 pub encoded: String,
28 pub lossless: bool,
30 pub semantic_identity: bool,
32 pub semantic_id: Option<String>,
34 pub span_count: usize,
36 pub diagnostics: Vec<String>,
38 pub trusted_executable: bool,
40 pub alternates: Vec<Symbol>,
42}
43
44impl ProbeResult {
45 pub fn lossless(encoded: impl Into<String>) -> Self {
47 Self {
48 encoded: encoded.into(),
49 lossless: true,
50 semantic_identity: true,
51 semantic_id: None,
52 span_count: 0,
53 diagnostics: Vec::new(),
54 trusted_executable: false,
55 alternates: Vec::new(),
56 }
57 }
58}
59
60pub fn roundtrip_probe(cx: &mut Cx, codec: &Symbol, value: &Expr) -> ProbeResult {
62 match encode_with_codec(cx, codec, value, EncodeOptions::default()) {
63 Ok(output) => {
64 let (encoded, input, prism_output) = render_output(output);
65 let decoded_lossless = decode_with_codec(cx, codec, input, ReadPolicy::default())
66 .map(|decoded| decoded.canonical_eq(value))
67 .unwrap_or(false);
68 let prism = prism_for_codec(codec);
69 let proof = match prism_output {
70 PrismOutput::Text(text) => prism.round_trip(cx, &text, EncodePosition::Data),
71 PrismOutput::Bytes(bytes) => {
72 prism.round_trip_bytes(cx, &bytes, EncodePosition::Data)
73 }
74 };
75 let semantic_identity = proof.loss_report.semantic_identity;
76 let diagnostics = diagnostic_text(&proof.loss_report.diagnostics);
77 ProbeResult {
78 encoded,
79 lossless: decoded_lossless && proof.loss_report.lossless,
80 semantic_identity,
81 semantic_id: proof.parse.semantic_id.as_ref().map(|id| id.stable.clone()),
82 span_count: proof.parse.span_map.len(),
83 diagnostics,
84 trusted_executable: proof.parse.inspection.trusted_executable,
85 alternates: suggested_alternates(codec),
86 }
87 }
88 Err(error) => ProbeResult {
89 encoded: format!("<encode error: {error}>"),
90 lossless: false,
91 semantic_identity: false,
92 semantic_id: None,
93 span_count: 0,
94 diagnostics: vec![format!("encode-error: {error}")],
95 trusted_executable: false,
96 alternates: suggested_alternates(codec),
97 },
98 }
99}
100
101fn render_output(output: Output) -> (String, Input, PrismOutput) {
102 match output {
103 Output::Text(text) => (
104 text.clone(),
105 Input::Text(text.clone()),
106 PrismOutput::Text(text),
107 ),
108 Output::Bytes(bytes) => {
109 let hex: String = bytes.iter().map(|byte| format!("{byte:02x}")).collect();
110 let display = format!("{} bytes: {hex}", bytes.len());
111 (
112 display,
113 Input::Bytes(bytes.clone()),
114 PrismOutput::Bytes(bytes),
115 )
116 }
117 }
118}
119
120fn prism_for_codec(codec: &Symbol) -> RuntimeCodecPrism {
121 match (&codec.namespace, &*codec.name) {
122 (Some(namespace), "chat") if &**namespace == "codec" => {
123 RuntimeCodecPrism::domain(codec.clone(), "chat transcript")
124 }
125 (Some(namespace), "mcp") if &**namespace == "codec" => {
126 RuntimeCodecPrism::domain(codec.clone(), "MCP envelope")
127 }
128 (Some(namespace), "binary") if &**namespace == "codec" => {
129 RuntimeCodecPrism::binary(codec.clone())
130 }
131 (Some(namespace), "binary-base64") if &**namespace == "codec" => {
132 RuntimeCodecPrism::binary_base64(codec.clone())
133 }
134 _ => RuntimeCodecPrism::general(codec.clone()),
135 }
136}
137
138fn suggested_alternates(codec: &Symbol) -> Vec<Symbol> {
139 let installed = ["lisp", "algol", "json", "binary", "binary-base64"];
140 installed
141 .iter()
142 .map(|name| Symbol::qualified("codec", *name))
143 .filter(|candidate| candidate != codec)
144 .collect()
145}
146
147fn diagnostic_text(diagnostics: &[PrismDiagnostic]) -> Vec<String> {
148 diagnostics
149 .iter()
150 .map(|diagnostic| format!("{}: {}", diagnostic.code, diagnostic.message))
151 .collect()
152}
153
154pub fn multi_codec_view(cx: &mut Cx, codecs: &[Symbol], value: &Expr) -> Expr {
157 let panels = codecs
158 .iter()
159 .map(|codec| codec_panel(cx, codec, value))
160 .collect();
161 node(
162 "stack",
163 vec![
164 ("role", sym("multi-codec")),
165 ("dir", sym("row")),
166 ("children", Expr::List(panels)),
167 ],
168 )
169}
170
171pub fn sysex_comparison_view(hex: &str, bytes: &[u8], lisp: &str, probe: &ProbeResult) -> Expr {
174 let status = if probe.lossless { "ok" } else { "warn" };
175 node(
176 "stack",
177 vec![
178 ("lens", Expr::Symbol(Symbol::new(SYSEX_COMPARISON_LENS))),
179 ("role", sym("sysex-comparison-view")),
180 ("dir", sym("column")),
181 (
182 "children",
183 Expr::List(vec![
184 node(
185 "table",
186 vec![
187 ("role", sym("sysex-format-comparison")),
188 (
189 "rows",
190 Expr::List(vec![
191 comparison_row("hex", hex),
192 comparison_row("binary", &binary_display(bytes)),
193 comparison_row("lisp", lisp),
194 ]),
195 ),
196 ],
197 ),
198 node(
199 "badge",
200 vec![
201 ("role", sym("round-trip-probe")),
202 ("status", sym(status)),
203 ("label", Expr::String(probe.encoded.clone())),
204 ],
205 ),
206 ]),
207 ),
208 ],
209 )
210}
211
212fn codec_panel(cx: &mut Cx, codec: &Symbol, value: &Expr) -> Expr {
213 let probe = roundtrip_probe(cx, codec, value);
214 let prism_report = prism_report(&probe);
215 let badge = node(
216 "badge",
217 vec![
218 ("status", sym(if probe.lossless { "ok" } else { "warn" })),
219 (
220 "label",
221 Expr::String(
222 if probe.lossless {
223 "round-trips"
224 } else {
225 "loss"
226 }
227 .to_owned(),
228 ),
229 ),
230 ],
231 );
232 let inner = node(
233 "box",
234 vec![
235 ("role", sym("codec-prism")),
236 (
237 "prism",
238 data_map(vec![
239 (
240 "semantic-id",
241 Expr::String(probe.semantic_id.clone().unwrap_or_default()),
242 ),
243 ("semantic-identity", bool_expr(probe.semantic_identity)),
244 ("span-count", int_expr(probe.span_count)),
245 ("trusted-executable", bool_expr(probe.trusted_executable)),
246 (
247 "alternates",
248 Expr::List(probe.alternates.iter().cloned().map(Expr::Symbol).collect()),
249 ),
250 ]),
251 ),
252 (
253 "children",
254 Expr::List(vec![
255 node("text", vec![("text", Expr::String(codec.to_string()))]),
256 badge,
257 prism_report,
258 node(
259 "field",
260 vec![
261 ("datatype", sym("text")),
262 ("value", Expr::String(probe.encoded)),
263 ],
264 ),
265 ]),
266 ),
267 ],
268 );
269 node(
270 "embed",
271 vec![("lens", Expr::Symbol(codec.clone())), ("scene", inner)],
272 )
273}
274
275fn prism_report(probe: &ProbeResult) -> Expr {
276 node(
277 "stack",
278 vec![
279 ("role", sym("prism-diagnostics")),
280 ("dir", sym("column")),
281 (
282 "children",
283 Expr::List(vec![
284 node(
285 "badge",
286 vec![
287 ("role", sym("prism-loss-report")),
288 ("status", sym(if probe.lossless { "ok" } else { "warn" })),
289 (
290 "label",
291 Expr::String(
292 if probe.semantic_identity {
293 "semantic identity"
294 } else {
295 "semantic loss"
296 }
297 .to_owned(),
298 ),
299 ),
300 ],
301 ),
302 data_map(vec![
303 ("spans", int_expr(probe.span_count)),
304 (
305 "diagnostics",
306 Expr::List(
307 probe
308 .diagnostics
309 .iter()
310 .cloned()
311 .map(Expr::String)
312 .collect(),
313 ),
314 ),
315 ]),
316 ]),
317 ),
318 ],
319 )
320}
321
322fn bool_expr(value: bool) -> Expr {
323 Expr::Symbol(Symbol::new(if value { "true" } else { "false" }))
324}
325
326fn int_expr(value: usize) -> Expr {
327 Expr::String(value.to_string())
328}
329
330fn comparison_row(format: &str, value: &str) -> Expr {
331 data_map(vec![
332 ("format", Expr::Symbol(Symbol::new(format))),
333 ("value", Expr::String(value.to_owned())),
334 ])
335}
336
337fn binary_display(bytes: &[u8]) -> String {
338 bytes
339 .iter()
340 .map(|byte| format!("{byte:08b}"))
341 .collect::<Vec<_>>()
342 .join(" ")
343}