Skip to main content

sim_shape/functions/
shape_object.rs

1//! `ShapeObject`: wraps a shape as a callable runtime object and provides the
2//! `shape_value` helpers that turn a shape into a kernel value.
3
4use std::sync::Arc;
5
6use sim_kernel::{
7    Args, Callable, ClassRef, Cx, DefaultFactory, Expr, Factory, Object, ObjectEncode,
8    ObjectEncoding, RawArgs, Result, ShapeRef, Symbol, Value,
9};
10
11use crate::base::{Shape, ShapeDoc, ShapeMatch};
12
13struct NamedShape {
14    symbol: Symbol,
15    shape: Arc<dyn Shape>,
16}
17
18impl NamedShape {
19    fn new(symbol: Symbol, shape: Arc<dyn Shape>) -> Self {
20        Self { symbol, shape }
21    }
22}
23
24impl Shape for NamedShape {
25    fn id(&self) -> Option<sim_kernel::ShapeId> {
26        self.shape.id()
27    }
28
29    fn symbol(&self) -> Option<Symbol> {
30        Some(self.symbol.clone())
31    }
32
33    fn parents(&self, cx: &mut Cx) -> Result<Vec<ShapeRef>> {
34        self.shape.parents(cx)
35    }
36
37    fn is_effectful(&self) -> bool {
38        self.shape.is_effectful()
39    }
40
41    fn is_total(&self) -> bool {
42        self.shape.is_total()
43    }
44
45    fn is_subshape_of(&self, cx: &mut Cx, parent: &dyn Shape) -> Result<Option<bool>> {
46        let parent = parent
47            .as_any()
48            .downcast_ref::<NamedShape>()
49            .map(|parent| parent.shape.as_ref())
50            .unwrap_or(parent);
51        self.shape.is_subshape_of(cx, parent)
52    }
53
54    fn check_value(&self, cx: &mut Cx, value: Value) -> Result<ShapeMatch> {
55        self.shape.check_value(cx, value)
56    }
57
58    fn check_expr(&self, cx: &mut Cx, expr: &Expr) -> Result<ShapeMatch> {
59        self.shape.check_expr(cx, expr)
60    }
61
62    fn describe(&self, cx: &mut Cx) -> Result<ShapeDoc> {
63        self.shape.describe(cx)
64    }
65}
66
67/// Runtime object wrapping a [`Shape`] so it is usable as a first-class value:
68/// a callable matcher, a kernel class, and (optionally) a re-encodable
69/// constructor expression.
70#[derive(Clone)]
71pub struct ShapeObject {
72    /// Symbol naming the wrapped shape.
73    pub symbol: Symbol,
74    /// The wrapped shape engine.
75    pub shape: Arc<dyn Shape>,
76    /// Constructor encoding used to round-trip the shape back to an expression.
77    pub encoding: Option<ObjectEncoding>,
78}
79
80impl ShapeObject {
81    /// Wrap a shape under a symbol with no constructor encoding.
82    pub fn new(symbol: Symbol, shape: Arc<dyn Shape>) -> Self {
83        Self {
84            symbol,
85            shape,
86            encoding: None,
87        }
88    }
89
90    /// Wrap a shape and record the constructor encoding used to re-encode it.
91    pub fn with_encoding(symbol: Symbol, shape: Arc<dyn Shape>, encoding: ObjectEncoding) -> Self {
92        Self {
93            symbol,
94            shape,
95            encoding: Some(encoding),
96        }
97    }
98
99    /// Describe the wrapped shape (name and details).
100    pub fn describe(&self, cx: &mut Cx) -> Result<ShapeDoc> {
101        self.shape.describe(cx)
102    }
103}
104
105impl Object for ShapeObject {
106    fn display(&self, cx: &mut Cx) -> Result<String> {
107        let doc = self.shape.describe(cx)?;
108        Ok(format!("#<shape {} {}>", self.symbol, doc.name))
109    }
110
111    fn as_any(&self) -> &dyn std::any::Any {
112        self
113    }
114}
115
116impl sim_kernel::ObjectCompat for ShapeObject {
117    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
118        if let Some(value) = cx.registry().class_by_symbol(&self.symbol) {
119            return Ok(value.clone());
120        }
121        if let Some(value) = cx
122            .registry()
123            .class_by_symbol(&Symbol::qualified("core", "Shape"))
124        {
125            return Ok(value.clone());
126        }
127        cx.factory().class_stub(
128            sim_kernel::CORE_SHAPE_CLASS_ID,
129            Symbol::qualified("core", "Shape"),
130        )
131    }
132    fn as_expr(&self, _cx: &mut Cx) -> Result<sim_kernel::Expr> {
133        match &self.encoding {
134            Some(ObjectEncoding::Constructor { class, args }) => Ok(sim_kernel::Expr::Call {
135                operator: Box::new(sim_kernel::Expr::Symbol(class.clone())),
136                args: args.clone(),
137            }),
138            _ => Ok(sim_kernel::Expr::Symbol(self.symbol.clone())),
139        }
140    }
141    fn as_table(&self, cx: &mut Cx) -> Result<Value> {
142        let doc = self.shape.describe(cx)?;
143        let mut entries = vec![
144            (Symbol::new("name"), cx.factory().string(doc.name)?),
145            (
146                Symbol::new("effectful"),
147                cx.factory().bool(self.shape.is_effectful())?,
148            ),
149            (
150                Symbol::new("total"),
151                cx.factory().bool(self.shape.is_total())?,
152            ),
153        ];
154        for (index, detail) in doc.details.into_iter().enumerate() {
155            entries.push((
156                Symbol::qualified("detail", index.to_string()),
157                cx.factory().string(detail)?,
158            ));
159        }
160        cx.factory().table(entries)
161    }
162    fn as_shape(&self) -> Option<&dyn Shape> {
163        Some(self.shape.as_ref())
164    }
165    fn as_callable(&self) -> Option<&dyn Callable> {
166        Some(self)
167    }
168    fn as_object_encoder(&self) -> Option<&dyn ObjectEncode> {
169        self.encoding.as_ref().map(|_| self as &dyn ObjectEncode)
170    }
171}
172
173impl ObjectEncode for ShapeObject {
174    fn object_encoding(&self, _cx: &mut Cx) -> Result<ObjectEncoding> {
175        self.encoding.clone().ok_or_else(|| {
176            sim_kernel::Error::Eval(format!("shape {} has no constructor encoding", self.symbol))
177        })
178    }
179}
180
181impl Callable for ShapeObject {
182    fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
183        let [value] = args.values() else {
184            return Err(sim_kernel::Error::Eval(
185                "shape call expects 1 argument".to_owned(),
186            ));
187        };
188        sim_kernel::call_shape(
189            cx,
190            self.shape.as_ref(),
191            sim_kernel::ShapeCallTarget::Value(value.clone()),
192        )
193    }
194
195    fn call_exprs(&self, cx: &mut Cx, args: RawArgs) -> Result<Value> {
196        let [expr] = args.exprs() else {
197            return Err(sim_kernel::Error::Eval(
198                "shape call expects 1 expression".to_owned(),
199            ));
200        };
201        sim_kernel::call_shape(
202            cx,
203            self.shape.as_ref(),
204            sim_kernel::ShapeCallTarget::Expr(expr.clone()),
205        )
206    }
207}
208
209/// Wrap a shape as a kernel value: an opaque [`ShapeObject`] that carries the
210/// given symbol as the shape's name and exposes it as a callable matcher.
211///
212/// # Examples
213///
214/// ```rust
215/// # use std::sync::Arc;
216/// # use sim_kernel::Symbol;
217/// # use sim_shape::{AnyShape, shape_value};
218/// let value = shape_value(Symbol::new("any"), Arc::new(AnyShape));
219/// assert!(value.object().as_shape().is_some());
220/// ```
221pub fn shape_value(symbol: Symbol, shape: Arc<dyn Shape>) -> Value {
222    DefaultFactory
223        .opaque(Arc::new(ShapeObject::new(
224            symbol.clone(),
225            Arc::new(NamedShape::new(symbol, shape)),
226        )))
227        .expect("shape object should always be boxable")
228}
229
230/// Like [`shape_value`] but also records the constructor encoding so the
231/// resulting value can be re-encoded back to its constructor expression.
232pub fn shape_value_with_encoding(
233    symbol: Symbol,
234    shape: Arc<dyn Shape>,
235    encoding: ObjectEncoding,
236) -> Value {
237    DefaultFactory
238        .opaque(Arc::new(ShapeObject::with_encoding(
239            symbol, shape, encoding,
240        )))
241        .expect("shape object should always be boxable")
242}