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