sim_lib_view_codec/
shape.rs1use sim_kernel::{Cx, Expr, Result, Symbol, Value};
9use sim_lib_scene::{node, sym};
10
11pub const SHAPE_LENS: &str = "view:codec-shape";
13
14pub fn shape_view(
17 cx: &mut Cx,
18 shape_value: &Value,
19 value: &Expr,
20 counterexample: &Expr,
21) -> Result<Expr> {
22 let shape = shape_value
23 .object()
24 .as_shape()
25 .ok_or_else(|| sim_kernel::Error::HostError("value is not a Shape".to_owned()))?;
26
27 let doc = shape.describe(cx)?;
28 let matched = shape.check_expr(cx, value)?;
29 let counter = shape.check_expr(cx, counterexample)?;
30
31 let mut sections = vec![
32 matcher_tree(&doc),
34 match_section(
36 "match",
37 value,
38 matched.accepted,
39 &bindings(&matched.captures),
40 &matched.diagnostics,
41 ),
42 ];
43 sections.push(match_section(
45 "counterexample",
46 counterexample,
47 counter.accepted,
48 &[],
49 &counter.diagnostics,
50 ));
51
52 Ok(node(
53 "box",
54 vec![
55 ("role", sym("shape")),
56 (
57 "shape",
58 Expr::Symbol(
59 shape
60 .symbol()
61 .unwrap_or_else(|| Symbol::new(doc.name.clone())),
62 ),
63 ),
64 ("children", Expr::List(sections)),
65 ],
66 ))
67}
68
69fn matcher_tree(doc: &sim_kernel::ShapeDoc) -> Expr {
70 let details = doc
71 .details
72 .iter()
73 .map(|detail| node("text", vec![("text", Expr::String(detail.clone()))]))
74 .collect();
75 node(
76 "tree",
77 vec![
78 ("label", Expr::String(format!("shape: {}", doc.name))),
79 ("nodes", Expr::List(details)),
80 ],
81 )
82}
83
84fn match_section(
85 role: &str,
86 value: &Expr,
87 accepted: bool,
88 bindings: &[String],
89 diagnostics: &[sim_kernel::Diagnostic],
90) -> Expr {
91 let mut children = vec![
92 node(
93 "text",
94 vec![("text", Expr::String(format!("value: {}", render(value))))],
95 ),
96 node(
97 "badge",
98 vec![
99 ("status", sym(if accepted { "ok" } else { "error" })),
100 (
101 "label",
102 Expr::String(if accepted { "matches" } else { "rejected" }.to_owned()),
103 ),
104 ],
105 ),
106 ];
107 if !bindings.is_empty() {
108 children.push(node(
109 "text",
110 vec![(
111 "text",
112 Expr::String(format!("bindings: {}", bindings.join(", "))),
113 )],
114 ));
115 }
116 for diagnostic in diagnostics {
117 children.push(node(
118 "text",
119 vec![
120 ("role", sym("diagnostic")),
121 ("text", Expr::String(diagnostic.message.clone())),
122 ],
123 ));
124 }
125 node(
126 "box",
127 vec![("role", sym(role)), ("children", Expr::List(children))],
128 )
129}
130
131fn bindings(captures: &sim_kernel::ShapeBindings) -> Vec<String> {
132 captures
133 .exprs()
134 .iter()
135 .map(|(name, _)| name.to_string())
136 .chain(captures.values().iter().map(|(name, _)| name.to_string()))
137 .collect()
138}
139
140fn render(value: &Expr) -> String {
141 match value {
142 Expr::Symbol(symbol) => symbol.as_qualified_str(),
143 Expr::Number(number) => number.canonical.clone(),
144 Expr::String(text) => format!("{text:?}"),
145 Expr::Bool(flag) => flag.to_string(),
146 Expr::Nil => "nil".to_owned(),
147 other => format!("<{other:?}>"),
148 }
149}