Skip to main content

sim_lib_scene/
shapes.rs

1//! Shapes for scene node kinds.
2//!
3//! Each baseline scene node kind gets a Shape that matches an `Expr::Map` whose
4//! `kind` tag equals that kind. View selection is overload selection over these
5//! Shapes, so the same matcher the kernel already uses for dispatch chooses
6//! lenses; there is no separate selection ladder. An umbrella `scene/Scene`
7//! Shape matches any recognized scene node and is used as the `codec:scene`
8//! expression shape.
9
10use std::sync::Arc;
11
12use sim_kernel::{Cx, Expr, MatchScore, Result, Shape, ShapeDoc, ShapeMatch, Symbol, Value};
13use sim_shape::{
14    ExactExprShape, ExprKind, ExprKindShape, NumberValueShape, OrShape, RepeatShape,
15    TableExtraPolicy, TableFieldSpec, TableShape,
16};
17
18use crate::kinds::{KIND_KEY, SCENE_KINDS, SCENE_NAMESPACE, scene_kind};
19
20struct RankedShape {
21    symbol: Symbol,
22    name: String,
23    detail: String,
24    score: MatchScore,
25    inner: Arc<dyn Shape>,
26}
27
28impl RankedShape {
29    fn ranked(&self, mut matched: ShapeMatch) -> ShapeMatch {
30        if matched.accepted {
31            matched.score = self.score;
32        }
33        matched
34    }
35}
36
37impl Shape for RankedShape {
38    fn symbol(&self) -> Option<Symbol> {
39        Some(self.symbol.clone())
40    }
41
42    fn is_effectful(&self) -> bool {
43        self.inner.is_effectful()
44    }
45
46    fn is_total(&self) -> bool {
47        self.inner.is_total()
48    }
49
50    fn check_value(&self, cx: &mut Cx, value: Value) -> Result<ShapeMatch> {
51        self.inner
52            .check_value(cx, value)
53            .map(|matched| self.ranked(matched))
54    }
55
56    fn check_expr(&self, cx: &mut Cx, expr: &Expr) -> Result<ShapeMatch> {
57        self.inner
58            .check_expr(cx, expr)
59            .map(|matched| self.ranked(matched))
60    }
61
62    fn describe(&self, _cx: &mut Cx) -> Result<ShapeDoc> {
63        Ok(ShapeDoc::new(self.name.clone()).with_detail(self.detail.clone()))
64    }
65}
66
67fn kind_field_shape(kind: Symbol) -> Arc<dyn Shape> {
68    Arc::new(TableShape::new(
69        vec![TableFieldSpec {
70            key: Symbol::new(KIND_KEY),
71            shape: Arc::new(ExactExprShape::new(Expr::Symbol(kind))),
72            required: true,
73        }],
74        TableExtraPolicy::Allow,
75    ))
76}
77
78fn field(key: &str, shape: Arc<dyn Shape>, required: bool) -> TableFieldSpec {
79    TableFieldSpec {
80        key: Symbol::new(key),
81        shape,
82        required,
83    }
84}
85
86fn expr_kind(kind: ExprKind) -> Arc<dyn Shape> {
87    Arc::new(ExprKindShape::new(kind))
88}
89
90fn heatmap_shape() -> Arc<dyn Shape> {
91    let number = || Arc::new(NumberValueShape) as Arc<dyn Shape>;
92    let footprint = Arc::new(TableShape::new(
93        vec![
94            field("cells", number(), true),
95            field("bytes", number(), true),
96        ],
97        TableExtraPolicy::Allow,
98    ));
99    Arc::new(TableShape::new(
100        vec![
101            field(
102                KIND_KEY,
103                Arc::new(ExactExprShape::new(Expr::Symbol(scene_kind("heatmap")))),
104                true,
105            ),
106            field("rows", number(), true),
107            field("cols", number(), true),
108            field("values", Arc::new(RepeatShape::new(number())), true),
109            field(
110                "valid",
111                Arc::new(RepeatShape::new(expr_kind(ExprKind::Bool))),
112                true,
113            ),
114            field("min", number(), true),
115            field("max", number(), true),
116            field("palette", expr_kind(ExprKind::Symbol), true),
117            field("label", expr_kind(ExprKind::String), true),
118            field("detector", expr_kind(ExprKind::String), true),
119            field("footprint", footprint, true),
120            field("advisory", expr_kind(ExprKind::String), false),
121        ],
122        TableExtraPolicy::Allow,
123    ))
124}
125
126fn kind_shape(name: &str) -> Arc<dyn Shape> {
127    if name == "heatmap" {
128        heatmap_shape()
129    } else {
130        kind_field_shape(scene_kind(name))
131    }
132}
133
134fn ranked_shape(
135    symbol: Symbol,
136    name: impl Into<String>,
137    detail: impl Into<String>,
138    score: i32,
139    inner: Arc<dyn Shape>,
140) -> Arc<dyn Shape> {
141    Arc::new(RankedShape {
142        symbol,
143        name: name.into(),
144        detail: detail.into(),
145        score: MatchScore::exact(score),
146        inner,
147    })
148}
149
150/// The symbol for the umbrella `scene/Scene` Shape.
151pub fn scene_shape_symbol() -> Symbol {
152    Symbol::qualified(SCENE_NAMESPACE, "Scene")
153}
154
155pub(crate) fn scene_shape() -> Arc<dyn Shape> {
156    let choices = SCENE_KINDS.iter().map(|name| kind_shape(name)).collect();
157    ranked_shape(
158        scene_shape_symbol(),
159        "Scene",
160        "any recognized scene node (a kind-tagged map)",
161        5,
162        Arc::new(OrShape::new(choices)),
163    )
164}
165
166fn scene_node_shape(name: &str) -> (Symbol, Arc<dyn Shape>) {
167    let symbol = Symbol::qualified(SCENE_NAMESPACE, capitalize(name));
168    let kind = scene_kind(name);
169    let shape = ranked_shape(
170        symbol.clone(),
171        symbol.name.to_string(),
172        format!("matches scene nodes tagged '{kind}'"),
173        20,
174        kind_shape(name),
175    );
176    (symbol, shape)
177}
178
179/// Build `(symbol, shape)` registrations for the umbrella Shape plus every
180/// baseline scene node kind Shape.
181pub fn scene_shape_specs() -> Vec<(Symbol, Arc<dyn Shape>)> {
182    let mut specs: Vec<(Symbol, Arc<dyn Shape>)> = vec![(scene_shape_symbol(), scene_shape())];
183    for name in SCENE_KINDS {
184        specs.push(scene_node_shape(name));
185    }
186    specs
187}
188
189fn capitalize(name: &str) -> String {
190    let mut chars = name.chars();
191    match chars.next() {
192        Some(first) => first.to_ascii_uppercase().to_string() + chars.as_str(),
193        None => String::new(),
194    }
195}