1use std::sync::Arc;
11
12use sim_kernel::{Cx, Expr, MatchScore, Result, Shape, ShapeDoc, ShapeMatch, Symbol, Value};
13use sim_shape::{ExactExprShape, OrShape, TableExtraPolicy, TableFieldSpec, TableShape};
14
15use crate::kinds::{KIND_KEY, SCENE_KINDS, SCENE_NAMESPACE, scene_kind};
16
17struct RankedShape {
18 symbol: Symbol,
19 name: String,
20 detail: String,
21 score: MatchScore,
22 inner: Arc<dyn Shape>,
23}
24
25impl RankedShape {
26 fn ranked(&self, mut matched: ShapeMatch) -> ShapeMatch {
27 if matched.accepted {
28 matched.score = self.score;
29 }
30 matched
31 }
32}
33
34impl Shape for RankedShape {
35 fn symbol(&self) -> Option<Symbol> {
36 Some(self.symbol.clone())
37 }
38
39 fn is_effectful(&self) -> bool {
40 self.inner.is_effectful()
41 }
42
43 fn is_total(&self) -> bool {
44 self.inner.is_total()
45 }
46
47 fn check_value(&self, cx: &mut Cx, value: Value) -> Result<ShapeMatch> {
48 self.inner
49 .check_value(cx, value)
50 .map(|matched| self.ranked(matched))
51 }
52
53 fn check_expr(&self, cx: &mut Cx, expr: &Expr) -> Result<ShapeMatch> {
54 self.inner
55 .check_expr(cx, expr)
56 .map(|matched| self.ranked(matched))
57 }
58
59 fn describe(&self, _cx: &mut Cx) -> Result<ShapeDoc> {
60 Ok(ShapeDoc::new(self.name.clone()).with_detail(self.detail.clone()))
61 }
62}
63
64fn kind_field_shape(kind: Symbol) -> Arc<dyn Shape> {
65 Arc::new(TableShape::new(
66 vec![TableFieldSpec {
67 key: Symbol::new(KIND_KEY),
68 shape: Arc::new(ExactExprShape::new(Expr::Symbol(kind))),
69 required: true,
70 }],
71 TableExtraPolicy::Allow,
72 ))
73}
74
75fn ranked_shape(
76 symbol: Symbol,
77 name: impl Into<String>,
78 detail: impl Into<String>,
79 score: i32,
80 inner: Arc<dyn Shape>,
81) -> Arc<dyn Shape> {
82 Arc::new(RankedShape {
83 symbol,
84 name: name.into(),
85 detail: detail.into(),
86 score: MatchScore::exact(score),
87 inner,
88 })
89}
90
91pub fn scene_shape_symbol() -> Symbol {
93 Symbol::qualified(SCENE_NAMESPACE, "Scene")
94}
95
96pub(crate) fn scene_shape() -> Arc<dyn Shape> {
97 let choices = SCENE_KINDS
98 .iter()
99 .map(|name| kind_field_shape(scene_kind(name)))
100 .collect();
101 ranked_shape(
102 scene_shape_symbol(),
103 "Scene",
104 "any recognized scene node (a kind-tagged map)",
105 5,
106 Arc::new(OrShape::new(choices)),
107 )
108}
109
110fn scene_node_shape(name: &str) -> (Symbol, Arc<dyn Shape>) {
111 let symbol = Symbol::qualified(SCENE_NAMESPACE, capitalize(name));
112 let kind = scene_kind(name);
113 let shape = ranked_shape(
114 symbol.clone(),
115 symbol.name.to_string(),
116 format!("matches scene nodes tagged '{kind}'"),
117 20,
118 kind_field_shape(kind),
119 );
120 (symbol, shape)
121}
122
123pub fn scene_shape_specs() -> Vec<(Symbol, Arc<dyn Shape>)> {
126 let mut specs: Vec<(Symbol, Arc<dyn Shape>)> = vec![(scene_shape_symbol(), scene_shape())];
127 for name in SCENE_KINDS {
128 specs.push(scene_node_shape(name));
129 }
130 specs
131}
132
133fn capitalize(name: &str) -> String {
134 let mut chars = name.chars();
135 match chars.next() {
136 Some(first) => first.to_ascii_uppercase().to_string() + chars.as_str(),
137 None => String::new(),
138 }
139}