1use sim_kernel::{CodecId, Cx, Expr, Result};
10use sim_lib_scene::{node, sym};
11
12use crate::contract::View;
13
14pub struct UniversalView;
16
17impl View for UniversalView {
18 fn encode(&self, _cx: &mut Cx, value: &Expr) -> Result<Expr> {
19 Ok(node(
20 "stack",
21 vec![
22 ("id", sym("universal")),
23 ("dir", sym("column")),
24 (
25 "children",
26 Expr::List(vec![
27 summary_card(value),
28 structure_tree(value),
29 canonical_text(value),
30 operations_inspector(value),
31 ]),
32 ),
33 ],
34 ))
35 }
36}
37
38fn text_line(text: String) -> Expr {
39 node("text", vec![("text", Expr::String(text))])
40}
41
42fn badge(status: &str, label: &str) -> Expr {
43 node(
45 "badge",
46 vec![
47 ("status", sym(status)),
48 ("label", Expr::String(label.to_owned())),
49 ],
50 )
51}
52
53fn summary_card(value: &Expr) -> Expr {
55 let roundtrip = roundtrip_badge(value);
56 node(
57 "box",
58 vec![
59 ("role", sym("summary")),
60 (
61 "children",
62 Expr::List(vec![
63 text_line(format!("kind: {}", expr_kind(value))),
64 text_line(format!("label: {}", short_label(value))),
65 roundtrip,
66 ]),
67 ),
68 ],
69 )
70}
71
72fn structure_tree(value: &Expr) -> Expr {
74 node(
75 "box",
76 vec![
77 ("role", sym("structure")),
78 ("children", Expr::List(vec![tree_of("value", value)])),
79 ],
80 )
81}
82
83fn tree_of(label: &str, value: &Expr) -> Expr {
84 match value {
85 Expr::Map(entries) => node(
86 "tree",
87 vec![
88 ("label", Expr::String(label.to_owned())),
89 (
90 "nodes",
91 Expr::List(
92 entries
93 .iter()
94 .map(|(key, child)| tree_of(&render_value(key), child))
95 .collect(),
96 ),
97 ),
98 ],
99 ),
100 Expr::List(items) | Expr::Vector(items) | Expr::Set(items) => node(
101 "tree",
102 vec![
103 ("label", Expr::String(format!("{label} [{}]", items.len()))),
104 (
105 "nodes",
106 Expr::List(
107 items
108 .iter()
109 .enumerate()
110 .map(|(index, child)| tree_of(&format!("[{index}]"), child))
111 .collect(),
112 ),
113 ),
114 ],
115 ),
116 atom => text_line(format!("{label}: {}", render_value(atom))),
117 }
118}
119
120fn canonical_text(value: &Expr) -> Expr {
127 let mut children = vec![text_line(render_value(value))];
128 match value {
129 Expr::Map(entries) => {
130 for (key, child) in entries {
131 if is_scalar(child) {
132 children.push(editable_leaf(value, key_path(key), child));
133 }
134 }
135 }
136 Expr::List(items) | Expr::Vector(items) | Expr::Set(items) => {
137 for (index, item) in items.iter().enumerate() {
138 if is_scalar(item) {
139 children.push(editable_leaf(value, index_path(index), item));
140 }
141 }
142 }
143 scalar => {
144 children.push(editable_leaf(scalar, Expr::List(Vec::new()), scalar));
148 }
149 }
150 node(
151 "box",
152 vec![
153 ("role", sym("canonical-text")),
154 ("children", Expr::List(children)),
155 ],
156 )
157}
158
159fn is_scalar(value: &Expr) -> bool {
161 !matches!(
162 value,
163 Expr::Map(_) | Expr::List(_) | Expr::Vector(_) | Expr::Set(_)
164 )
165}
166
167fn key_path(key: &Expr) -> Expr {
169 Expr::List(vec![Expr::Vector(vec![sym("k"), key.clone()])])
170}
171
172fn index_path(index: usize) -> Expr {
174 Expr::List(vec![Expr::Vector(vec![
175 sym("i"),
176 Expr::String(index.to_string()),
177 ])])
178}
179
180fn editable_leaf(root: &Expr, path: Expr, leaf: &Expr) -> Expr {
184 node(
185 "field",
186 vec![
187 ("kind", sym("text")),
188 ("value", Expr::String(render_value(leaf))),
189 ("target", root.clone()),
190 ("path", path),
191 ("readonly", Expr::Bool(false)),
192 ],
193 )
194}
195
196fn operations_inspector(value: &Expr) -> Expr {
198 node(
199 "stack",
200 vec![
201 ("role", sym("operations")),
202 ("dir", sym("column")),
203 (
204 "children",
205 Expr::List(vec![
206 action_button("copy", "Copy", value),
207 action_button("edit", "Edit", value),
208 ]),
209 ),
210 ],
211 )
212}
213
214fn action_button(control: &str, label: &str, value: &Expr) -> Expr {
215 node(
216 "button",
217 vec![
218 ("control", sym(control)),
219 ("label", Expr::String(label.to_owned())),
220 ("target", value.clone()),
221 ],
222 )
223}
224
225fn roundtrip_badge(value: &Expr) -> Expr {
226 let codec = CodecId(0);
227 match sim_codec::encode_portable(codec, value) {
228 Ok(text) => match sim_codec::decode_portable(codec, &text) {
229 Ok(decoded) if &decoded == value => badge("ok", "round-trips"),
230 Ok(_) => badge("warn", "round-trip differs"),
231 Err(_) => badge("warn", "decode failed"),
232 },
233 Err(_) => badge("info", "non-data value"),
234 }
235}
236
237pub(crate) fn universal_regions(value: &Expr) -> Vec<Expr> {
240 vec![
241 summary_card(value),
242 canonical_text(value),
243 structure_tree(value),
244 operations_inspector(value),
245 ]
246}
247
248pub use sim_value::kind::expr_kind;
250
251fn short_label(value: &Expr) -> String {
252 let rendered = render_value(value);
253 if rendered.len() <= 48 {
254 rendered
255 } else {
256 format!("{}...", &rendered[..45])
257 }
258}
259
260pub fn render_value(value: &Expr) -> String {
262 match value {
263 Expr::Nil => "nil".to_owned(),
264 Expr::Bool(flag) => flag.to_string(),
265 Expr::Number(number) => number.canonical.clone(),
266 Expr::Symbol(symbol) | Expr::Local(symbol) => symbol.as_qualified_str(),
267 Expr::String(text) => format!("{text:?}"),
268 Expr::Bytes(bytes) => format!("#bytes({})", bytes.len()),
269 Expr::List(items) => format!("({})", render_items(items)),
270 Expr::Vector(items) => format!("[{}]", render_items(items)),
271 Expr::Set(items) => format!("#{{{}}}", render_items(items)),
272 Expr::Map(entries) => {
273 let body = entries
274 .iter()
275 .map(|(key, value)| format!("{}: {}", render_value(key), render_value(value)))
276 .collect::<Vec<_>>()
277 .join(", ");
278 format!("{{{body}}}")
279 }
280 other => format!("<{}>", expr_kind(other)),
281 }
282}
283
284fn render_items(items: &[Expr]) -> String {
285 items.iter().map(render_value).collect::<Vec<_>>().join(" ")
286}