1use sim_kernel::{Expr, LoadCx, Result, Symbol, Value};
4
5mod examples;
6mod specs;
7
8pub use examples::topology_example_specs;
9pub use specs::{topology_function_specs, topology_verb_specs};
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub struct TopologyFunctionSpec {
14 pub symbol: &'static str,
16 pub summary: &'static str,
18 pub detail: &'static str,
20 pub args: &'static str,
22 pub result: &'static str,
24}
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub struct TopologyVerbSpec {
29 pub name: &'static str,
31 pub summary: &'static str,
33 pub detail: &'static str,
35}
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39pub struct TopologyExampleSpec {
40 pub name: &'static str,
42 pub summary: &'static str,
44 pub package: &'static str,
46}
47
48pub fn topology_browse_symbols() -> Vec<Symbol> {
51 let function_cards = topology_function_specs()
52 .into_iter()
53 .map(|spec| card_symbol("function", spec.symbol));
54 let verb_cards = topology_verb_specs()
55 .into_iter()
56 .map(|spec| Symbol::qualified("topology/verb", spec.name));
57 let examples = topology_example_specs()
58 .into_iter()
59 .map(|spec| Symbol::qualified("topology/example", spec.name));
60 function_cards
61 .chain(verb_cards)
62 .chain([Symbol::qualified("topology", "package-format")])
63 .chain(examples)
64 .collect()
65}
66
67pub fn topology_browse_value(cx: &mut LoadCx, symbol: Symbol) -> Result<Value> {
69 cx.factory().expr(topology_card_expr(&symbol))
70}
71
72pub fn topology_card_expr(symbol: &Symbol) -> Expr {
75 if let Some(name) = symbol.name.as_ref().strip_prefix("function-")
76 && let Some(spec) = topology_function_specs()
77 .into_iter()
78 .find(|spec| spec.symbol == name)
79 {
80 return function_card(spec);
81 }
82 if symbol.namespace.as_deref() == Some("topology/verb")
83 && let Some(spec) = topology_verb_specs()
84 .into_iter()
85 .find(|spec| spec.name == symbol.name.as_ref())
86 {
87 return verb_card(spec);
88 }
89 if symbol == &Symbol::qualified("topology", "package-format") {
90 return package_format_card();
91 }
92 if symbol.namespace.as_deref() == Some("topology/example")
93 && let Some(spec) = topology_example_specs()
94 .into_iter()
95 .find(|spec| spec.name == symbol.name.as_ref())
96 {
97 return example_card(spec);
98 }
99 card_v2(CardV2Spec {
100 subject: symbol.clone(),
101 kind: Symbol::qualified("topology", "unknown"),
102 summary: "unknown topology browse subject".to_owned(),
103 detail: String::new(),
104 args: Expr::Symbol(Symbol::qualified("core", "Any")),
105 result: Expr::Symbol(Symbol::qualified("core", "Any")),
106 tests: Vec::new(),
107 see_also: Vec::new(),
108 })
109}
110
111fn function_card(spec: TopologyFunctionSpec) -> Expr {
112 card_v2(CardV2Spec {
113 subject: Symbol::qualified("topology", spec.symbol),
114 kind: Symbol::qualified("core", "function"),
115 summary: spec.summary.to_owned(),
116 detail: spec.detail.to_owned(),
117 args: Expr::String(spec.args.to_owned()),
118 result: Expr::String(spec.result.to_owned()),
119 tests: Vec::new(),
120 see_also: vec![
121 Expr::Symbol(Symbol::qualified("topology", "package-format")),
122 Expr::Symbol(Symbol::qualified("topology/card", "function-test")),
123 ],
124 })
125}
126
127fn verb_card(spec: TopologyVerbSpec) -> Expr {
128 card_v2(CardV2Spec {
129 subject: Symbol::qualified("topology/verb", spec.name),
130 kind: Symbol::qualified("topology", "node-verb"),
131 summary: spec.summary.to_owned(),
132 detail: spec.detail.to_owned(),
133 args: Expr::String("node input ports".to_owned()),
134 result: Expr::String("node output ports".to_owned()),
135 tests: Vec::new(),
136 see_also: vec![Expr::Symbol(Symbol::qualified(
137 "topology",
138 "package-format",
139 ))],
140 })
141}
142
143fn package_format_card() -> Expr {
144 let tests = topology_example_specs()
145 .into_iter()
146 .map(example_test_card)
147 .collect::<Vec<_>>();
148 card_v2(CardV2Spec {
149 subject: Symbol::qualified("topology", "package-format"),
150 kind: Symbol::qualified("topology", "package-format"),
151 summary: "section-based .simtopo package format".to_owned(),
152 detail: "Packages contain graph, tests, metadata, and capabilities sections. Graph and tests reuse the topology text DSL.".to_owned(),
153 args: Expr::String("package source text".to_owned()),
154 result: Expr::String("TopologyPackage graph, tests, metadata, and capabilities".to_owned()),
155 tests,
156 see_also: topology_example_specs()
157 .into_iter()
158 .map(|spec| Expr::Symbol(Symbol::qualified("topology/example", spec.name)))
159 .collect(),
160 })
161}
162
163fn example_card(spec: TopologyExampleSpec) -> Expr {
164 card_v2(CardV2Spec {
165 subject: Symbol::qualified("topology/example", spec.name),
166 kind: Symbol::qualified("core", "test"),
167 summary: spec.summary.to_owned(),
168 detail: "Generated .simtopo package example that can be run with topology/test.".to_owned(),
169 args: Expr::String(spec.package.to_owned()),
170 result: Expr::String("topology test report".to_owned()),
171 tests: vec![example_test_card(spec)],
172 see_also: vec![Expr::Symbol(Symbol::qualified(
173 "topology",
174 "package-format",
175 ))],
176 })
177}
178
179struct CardV2Spec {
180 subject: Symbol,
181 kind: Symbol,
182 summary: String,
183 detail: String,
184 args: Expr,
185 result: Expr,
186 tests: Vec<Expr>,
187 see_also: Vec<Expr>,
188}
189
190fn card_v2(spec: CardV2Spec) -> Expr {
191 let CardV2Spec {
192 subject,
193 kind,
194 summary,
195 detail,
196 args,
197 result,
198 tests,
199 see_also,
200 } = spec;
201 let coverage = coverage_expr(&tests);
202 Expr::Map(vec![
203 entry("subject", Expr::Symbol(subject.clone())),
204 entry("kind", Expr::Symbol(kind)),
205 entry(
206 "help",
207 help_expr(subject, summary, detail, see_also.clone()),
208 ),
209 entry("args", args),
210 entry("result", result),
211 entry("tests", Expr::List(tests)),
212 entry("ops", Expr::List(Vec::new())),
213 entry("requires", Expr::List(Vec::new())),
214 entry("see-also", Expr::List(see_also)),
215 entry("shape-known", Expr::Bool(true)),
216 entry("facets", Expr::List(Vec::new())),
217 entry("coverage", coverage),
218 entry("provenance", Expr::List(Vec::new())),
219 entry("freshness", Expr::Symbol(Symbol::new("fresh"))),
220 ])
221}
222
223fn help_expr(subject: Symbol, summary: String, detail: String, see_also: Vec<Expr>) -> Expr {
224 Expr::Map(vec![
225 entry("subject", Expr::Symbol(subject)),
226 entry("kind", Expr::Symbol(Symbol::qualified("core", "function"))),
227 entry("summary", Expr::String(summary)),
228 entry("detail", Expr::String(detail)),
229 entry(
230 "exported-by",
231 Expr::Symbol(Symbol::qualified("sim", "topology")),
232 ),
233 entry("stability", Expr::Symbol(Symbol::new("experimental"))),
234 entry("capabilities", Expr::List(Vec::new())),
235 entry("demand", Expr::List(Vec::new())),
236 entry("see-also", Expr::List(see_also)),
237 ])
238}
239
240fn example_test_card(spec: TopologyExampleSpec) -> Expr {
241 Expr::Map(vec![
242 entry(
243 "name",
244 Expr::Symbol(Symbol::qualified("topology/example", spec.name)),
245 ),
246 entry(
247 "subjects",
248 Expr::List(vec![Expr::Symbol(Symbol::qualified(
249 "topology",
250 "package-format",
251 ))]),
252 ),
253 entry("lib", Expr::Symbol(Symbol::qualified("sim", "topology"))),
254 entry("mode", Expr::Symbol(Symbol::new("example"))),
255 entry(
256 "expr",
257 Expr::Call {
258 operator: Box::new(Expr::Symbol(Symbol::qualified("topology", "test"))),
259 args: vec![Expr::String(spec.package.to_owned())],
260 },
261 ),
262 entry(
263 "expr-codec",
264 Expr::Symbol(Symbol::qualified("codec", "lisp")),
265 ),
266 entry("expected", Expr::Nil),
267 entry("expected-codec", Expr::Nil),
268 entry("expected-error", Expr::Nil),
269 entry("codecs", Expr::List(Vec::new())),
270 entry("example", Expr::Bool(true)),
271 entry("capabilities", Expr::List(Vec::new())),
272 ])
273}
274
275fn coverage_expr(tests: &[Expr]) -> Expr {
276 Expr::Map(vec![
277 entry("tests", number_expr(tests.len() as u64)),
278 entry("examples", number_expr(example_count(tests) as u64)),
279 entry("runnable", Expr::Bool(!tests.is_empty())),
280 entry("passed", Expr::Nil),
281 entry("failed", Expr::Nil),
282 entry("skipped", Expr::Nil),
283 entry("last-run", Expr::Nil),
284 entry("stale", Expr::Bool(false)),
285 ])
286}
287
288fn example_count(tests: &[Expr]) -> usize {
289 tests
290 .iter()
291 .filter(|test| map_bool_field(test, "example") == Some(true))
292 .count()
293}
294
295fn map_bool_field(expr: &Expr, name: &str) -> Option<bool> {
296 let Expr::Map(entries) = expr else {
297 return None;
298 };
299 entries.iter().find_map(|(key, value)| match (key, value) {
300 (Expr::Symbol(symbol), Expr::Bool(value))
301 if symbol.namespace.is_none() && symbol.name.as_ref() == name =>
302 {
303 Some(*value)
304 }
305 _ => None,
306 })
307}
308
309fn card_symbol(kind: &str, name: &str) -> Symbol {
310 Symbol::qualified("topology/card", format!("{kind}-{name}"))
311}
312
313use sim_value::build::{entry, uint as number_expr};