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::{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
91/// The symbol for the umbrella `scene/Scene` Shape.
92pub 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
123/// Build `(symbol, shape)` registrations for the umbrella Shape plus every
124/// baseline scene node kind Shape.
125pub 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}