Skip to main content

sim_shape/
functions.rs

1//! The callable shape object: function objects with shape-typed cases,
2//! overload selection across those cases, and shape-as-value wrapping.
3
4use std::sync::Arc;
5
6mod demand;
7mod select;
8mod shape_object;
9
10#[cfg(test)]
11mod tests;
12
13use sim_kernel::{
14    Args, Callable, ClassRef, Cx, Demand, FunctionId, Object, PreparedArgs, RawArgs,
15    ReadConstructor, Result, ShapeId, ShapeRef, Symbol, Value,
16};
17
18use crate::base::{Bindings, Shape, ShapeMatch};
19use crate::primitives::OneOfShape;
20pub use shape_object::{ShapeObject, shape_value, shape_value_with_encoding};
21
22/// Native implementation backing a single [`FunctionCase`].
23///
24/// Invoked with the forced/prepared arguments and the bindings captured while
25/// the case's argument shape matched.
26pub type NativeFunctionImpl = fn(&mut Cx, &PreparedArgs, Bindings) -> Result<Value>;
27
28/// One overload case of a [`FunctionObject`]: a shape-typed signature paired
29/// with the native code that runs when it is selected.
30#[derive(Clone)]
31pub struct FunctionCase {
32    /// Stable identifier for this case within the function.
33    pub id: sim_kernel::CaseId,
34    /// Symbol naming the case.
35    pub name: Symbol,
36    /// Shape the argument list must match for this case to apply.
37    pub args: Arc<dyn Shape>,
38    /// Optional shape the result is checked against after the call.
39    pub result: Option<Arc<dyn Shape>>,
40    /// Per-argument evaluation demand (how far each argument is forced).
41    pub demand: Vec<sim_kernel::Demand>,
42    /// Tie-break priority; higher wins before match score is consulted.
43    pub priority: i32,
44    /// Native code run when this case is selected.
45    pub implementation: NativeFunctionImpl,
46}
47
48/// A callable function object: a named set of shape-typed overload cases with
49/// selection driven by case priority and argument match score.
50#[derive(Clone)]
51pub struct FunctionObject {
52    /// Stable function identifier.
53    pub id: FunctionId,
54    /// Symbol naming the function.
55    pub symbol: Symbol,
56    /// Overload cases in registration order.
57    pub cases: Vec<FunctionCase>,
58}
59
60/// The case chosen by overload selection together with its match result.
61#[derive(Clone)]
62pub struct SelectedCase<'a> {
63    /// The selected overload case.
64    pub case: &'a FunctionCase,
65    /// The match (score, captures, diagnostics) that selected it.
66    pub match_result: ShapeMatch,
67}
68
69impl FunctionObject {
70    /// Build a function object from an id, symbol, and its overload cases.
71    pub fn new(id: FunctionId, symbol: Symbol, cases: Vec<FunctionCase>) -> Self {
72        Self { id, symbol, cases }
73    }
74
75    /// Shape accepting any case's arguments: the lone case's shape, or a
76    /// one-of over every case. `None` when the function has no cases.
77    pub fn combined_args_shape(&self) -> Option<Arc<dyn Shape>> {
78        match self.cases.as_slice() {
79            [] => None,
80            [one] => Some(one.args.clone()),
81            many => Some(Arc::new(OneOfShape::new(
82                many.iter().map(|case| case.args.clone()).collect(),
83            ))),
84        }
85    }
86
87    /// Shape covering every case's result, or `None` if any case omits a
88    /// result shape. A single result is returned directly; many become a
89    /// one-of.
90    pub fn combined_result_shape(&self) -> Option<Arc<dyn Shape>> {
91        let shapes = self
92            .cases
93            .iter()
94            .map(|case| case.result.clone())
95            .collect::<Option<Vec<_>>>()?;
96        match shapes.as_slice() {
97            [] => None,
98            [one] => Some(one.clone()),
99            many => Some(Arc::new(OneOfShape::new(many.to_vec()))),
100        }
101    }
102
103    /// Evaluation demand declared for argument `index` across all cases.
104    ///
105    /// Returns the shared demand when every case agrees, [`Demand::Value`] when
106    /// they disagree, and `None` when no case declares that position.
107    pub fn declared_demand(&self, index: usize) -> Option<Demand> {
108        let mut declared = None;
109        for case in &self.cases {
110            let case_demand = case.demand.get(index).copied().unwrap_or(Demand::Value);
111            match declared {
112                None => declared = Some(case_demand),
113                Some(existing) if existing == case_demand => {}
114                Some(_) => return Some(Demand::Value),
115            }
116        }
117        declared
118    }
119
120    /// Per-position demands for the call, sized to the widest case and
121    /// defaulting unspecified positions to [`Demand::Value`].
122    pub fn declared_demands(&self) -> Vec<Demand> {
123        let max_len = self
124            .cases
125            .iter()
126            .map(|case| case.demand.len())
127            .max()
128            .unwrap_or(0);
129        (0..max_len)
130            .map(|index| self.declared_demand(index).unwrap_or(Demand::Value))
131            .collect()
132    }
133}
134
135impl Object for FunctionObject {
136    fn display(&self, _cx: &mut Cx) -> Result<String> {
137        Ok(format!("#<function {}>", self.symbol))
138    }
139
140    fn as_any(&self) -> &dyn std::any::Any {
141        self
142    }
143}
144
145impl sim_kernel::ObjectCompat for FunctionObject {
146    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
147        if let Some(value) = cx
148            .registry()
149            .class_by_symbol(&Symbol::qualified("core", "Function"))
150        {
151            return Ok(value.clone());
152        }
153        cx.factory().class_stub(
154            sim_kernel::CORE_FUNCTION_CLASS_ID,
155            Symbol::qualified("core", "Function"),
156        )
157    }
158    fn as_table(&self, cx: &mut Cx) -> Result<Value> {
159        let mut entries = vec![
160            (
161                Symbol::new("symbol"),
162                cx.factory().string(self.symbol.to_string())?,
163            ),
164            (
165                Symbol::new("case-count"),
166                cx.factory().number_literal(
167                    Symbol::qualified("numbers", "f64"),
168                    self.cases.len().to_string(),
169                )?,
170            ),
171        ];
172        for (index, case) in self.cases.iter().enumerate() {
173            entries.push((
174                Symbol::qualified("case", case.name.name.clone()),
175                cx.factory().string(case.name.to_string())?,
176            ));
177            let args_doc = case.args.describe(cx)?;
178            entries.push((
179                Symbol::qualified("case-args", index.to_string()),
180                cx.factory().string(args_doc.name)?,
181            ));
182            if let Some(result) = &case.result {
183                let result_doc = result.describe(cx)?;
184                entries.push((
185                    Symbol::qualified("case-result", index.to_string()),
186                    cx.factory().string(result_doc.name)?,
187                ));
188            }
189            if !case.demand.is_empty() {
190                entries.push((
191                    Symbol::qualified("case-demand", index.to_string()),
192                    cx.factory().list(
193                        case.demand
194                            .iter()
195                            .map(|demand| {
196                                let name = match demand {
197                                    Demand::Never => "never",
198                                    Demand::Bool => "bool",
199                                    Demand::Value => "value",
200                                    Demand::Expr => "expr",
201                                    Demand::Class(_) => "class",
202                                    Demand::Shape(_) => "shape",
203                                };
204                                cx.factory().symbol(Symbol::new(name))
205                            })
206                            .collect::<Result<Vec<_>>>()?,
207                    )?,
208                ));
209            }
210        }
211        cx.factory().table(entries)
212    }
213    fn as_callable(&self) -> Option<&dyn Callable> {
214        Some(self)
215    }
216    fn as_read_constructor(&self) -> Option<&dyn ReadConstructor> {
217        Some(self)
218    }
219}
220
221impl Callable for FunctionObject {
222    fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
223        let prepared = PreparedArgs::new(args.into_vec());
224        let selected = self.select_case(cx, &prepared)?;
225        let prepared = refine_prepared_args(cx, &prepared, selected.case)?;
226        let bindings = selected.match_result.captures;
227        let env = bindings.clone().into_child_env(cx)?;
228        let result = cx.with_env(env, |cx| {
229            (selected.case.implementation)(cx, &prepared, bindings)
230        })?;
231
232        if let Some(shape) = &selected.case.result {
233            let matched = shape.check_value(cx, result.clone())?;
234            if !matched.accepted {
235                return Err(sim_kernel::Error::WrongShape {
236                    expected: shape.id().unwrap_or(ShapeId(0)),
237                    diagnostics: matched.diagnostics,
238                });
239            }
240        }
241
242        Ok(result)
243    }
244
245    fn browse_args_shape(&self, _cx: &mut Cx) -> Result<Option<ShapeRef>> {
246        Ok(self
247            .combined_args_shape()
248            .map(|shape| shape_value(Symbol::qualified(self.symbol.to_string(), "args"), shape)))
249    }
250
251    fn browse_result_shape(&self, _cx: &mut Cx) -> Result<Option<ShapeRef>> {
252        Ok(self
253            .combined_result_shape()
254            .map(|shape| shape_value(Symbol::qualified(self.symbol.to_string(), "result"), shape)))
255    }
256
257    fn call_exprs(&self, cx: &mut Cx, args: RawArgs) -> Result<Value> {
258        self.call_exprs_with_demands(cx, args)
259    }
260}
261
262fn refine_prepared_args(
263    cx: &mut Cx,
264    prepared: &PreparedArgs,
265    case: &FunctionCase,
266) -> Result<PreparedArgs> {
267    let mut values = Vec::with_capacity(prepared.len());
268    for index in 0..prepared.len() {
269        let value = prepared
270            .get(index)
271            .cloned()
272            .ok_or_else(|| sim_kernel::Error::Eval(format!("missing prepared arg {index}")))?;
273        let demand = case.demand.get(index).copied().unwrap_or(Demand::Value);
274        values.push(force_for_case_demand(cx, value, demand)?);
275    }
276    Ok(PreparedArgs::new(values))
277}
278
279fn force_for_case_demand(cx: &mut Cx, value: Value, demand: Demand) -> Result<Value> {
280    match demand {
281        Demand::Shape(shape_id) => {
282            let value = cx.force(value, Demand::Value)?;
283            let shape_value = cx
284                .registry()
285                .shape_value(shape_id)
286                .cloned()
287                .ok_or_else(|| sim_kernel::Error::WrongShape {
288                    expected: shape_id,
289                    diagnostics: Vec::new(),
290                })?;
291            let shape = shape_value
292                .object()
293                .as_shape()
294                .ok_or(sim_kernel::Error::TypeMismatch {
295                    expected: "shape object",
296                    found: "non-shape object",
297                })?;
298            let matched = shape.check_value(cx, value.clone())?;
299            if matched.accepted {
300                Ok(value)
301            } else {
302                Err(sim_kernel::Error::WrongShape {
303                    expected: shape_id,
304                    diagnostics: matched.diagnostics,
305                })
306            }
307        }
308        other => cx.force(value, other),
309    }
310}
311
312impl ReadConstructor for FunctionObject {
313    fn symbol(&self) -> Symbol {
314        self.symbol.clone()
315    }
316
317    fn args_shape(&self, cx: &mut Cx) -> Result<ShapeRef> {
318        match self.combined_args_shape() {
319            Some(shape) => Ok(shape_value(
320                Symbol::qualified(self.symbol.to_string(), "args-shape"),
321                shape,
322            )),
323            None => cx.factory().nil(),
324        }
325    }
326
327    fn construct_read(&self, cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
328        self.call(cx, Args::new(args))
329    }
330}
331
332/// Merge several functions into one whose cases are the union of theirs.
333///
334/// The result is a fresh [`FunctionObject`] with a generated `overload:` symbol
335/// and a new function id; selection then ranks across all combined cases.
336pub fn overload(cx: &mut Cx, functions: Vec<FunctionObject>) -> Result<FunctionObject> {
337    let mut cases = Vec::new();
338    let mut names = Vec::new();
339
340    for function in functions {
341        names.push(function.symbol.to_string());
342        cases.extend(function.cases);
343    }
344
345    let symbol = Symbol::new(format!("overload:{}", names.join("+")));
346    Ok(FunctionObject {
347        id: cx.registry_mut().fresh_function_id(),
348        symbol,
349        cases,
350    })
351}
352
353/// Borrow the overload cases of a function object.
354pub fn function_cases(function: &FunctionObject) -> &[FunctionCase] {
355    &function.cases
356}
357
358/// Borrow the argument shape of a single case.
359pub fn case_shape(case: &FunctionCase) -> &dyn Shape {
360    case.args.as_ref()
361}
362
363/// Borrow the result shape of a case, if it declares one.
364pub fn case_result_shape(case: &FunctionCase) -> Option<&dyn Shape> {
365    case.result.as_deref()
366}