Skip to main content

microcad_lang/eval/call/
call_method.rs

1// Copyright © 2025-2026 The µcad authors <info@microcad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! Argument value evaluation entity
5
6use microcad_lang_base::{PushDiag, SrcRef};
7
8use crate::{eval::*, model::Model, symbol::SymbolDef, syntax::*};
9
10/// Trait for calling methods of values
11pub trait CallMethod<T = Value> {
12    /// Evaluate method call into a value (if possible)
13    ///
14    /// - `name`: Name of the method
15    /// - `args`: Arguments for the method
16    /// - `context`: Evaluation context
17    fn call_method(
18        &self,
19        id: &QualifiedName,
20        args: &ArgumentValueList,
21        context: &mut EvalContext,
22    ) -> EvalResult<T>;
23}
24
25impl CallMethod for Array {
26    fn call_method(
27        &self,
28        id: &QualifiedName,
29        _: &ArgumentValueList,
30        context: &mut EvalContext,
31    ) -> EvalResult<Value> {
32        Ok(
33            match id.single_identifier().expect("Single id").id().as_str() {
34                "count" => self.len().into(),
35                "first" | "head" => self.first(), // Keep head method as deprecated method.
36                "last" => self.last(),
37                "tail" => self.tail().into(),
38                "rev" => self.rev().into(),
39                "sorted" => self.sorted().into(),
40                "all_equal" => self.all_equal().into(),
41                "is_ascending" => self.is_ascending().into(),
42                "is_descending" => self.is_descending().into(),
43                _ => {
44                    context.error(id, EvalError::UnknownMethod(id.clone()))?;
45                    Value::None
46                }
47            },
48        )
49    }
50}
51
52impl CallMethod<Option<Model>> for Model {
53    fn call_method(
54        &self,
55        name: &QualifiedName,
56        args: &ArgumentValueList,
57        context: &mut EvalContext,
58    ) -> EvalResult<Option<Model>> {
59        match context.lookup(name, LookupTarget::Method) {
60            Ok(symbol) => context.scope(
61                StackFrame::Call {
62                    symbol: symbol.clone(),
63                    args: args.clone(),
64                    src_ref: SrcRef::merge(name, args),
65                },
66                |context| {
67                    symbol.with_def(|def| match def {
68                        SymbolDef::Workbench(workbench_definition) => {
69                            let model = workbench_definition.call(
70                                SrcRef::merge(name, args),
71                                symbol.clone(),
72                                args,
73                                context,
74                            )?;
75
76                            Ok::<_, EvalError>(Some(model.replace_input_placeholders(self)))
77                        }
78                        SymbolDef::Builtin(builtin) => match builtin.call(args, context)? {
79                            Value::Model(model) => Ok(Some(model.replace_input_placeholders(self))),
80                            value => panic!("Builtin call returned {value} but no models."),
81                        },
82                        _ => {
83                            context.error(name, EvalError::SymbolCannotBeCalled(name.clone()))?;
84                            Ok(None)
85                        }
86                    })
87                },
88            ),
89            Err(err) => {
90                context.error(name, err)?;
91                Ok(None)
92            }
93        }
94    }
95}
96
97impl CallMethod for Value {
98    fn call_method(
99        &self,
100        id: &QualifiedName,
101        args: &ArgumentValueList,
102        context: &mut EvalContext,
103    ) -> EvalResult<Value> {
104        match self {
105            Value::Integer(_) => eval_todo!(context, id, "call_method for Integer"),
106            Value::Quantity(_) => eval_todo!(context, id, "call_method for Quantity"),
107            Value::Bool(_) => eval_todo!(context, id, "call_method for Bool"),
108            Value::String(_) => eval_todo!(context, id, "call_method for String"),
109            Value::Tuple(_) => eval_todo!(context, id, "call_method for Tuple"),
110            Value::Matrix(_) => eval_todo!(context, id, "call_method for Matrix"),
111            Value::Array(array) => array.call_method(id, args, context),
112            Value::Model(model) => Ok(model
113                .call_method(id, args, context)?
114                .map(Value::Model)
115                .unwrap_or_default()),
116            _ => {
117                context.error(id, EvalError::UnknownMethod(id.clone()))?;
118                Ok(Value::None)
119            }
120        }
121    }
122}
123
124#[test]
125fn call_list_method() {
126    let list = Array::from_values(
127        ValueList::new(vec![
128            Value::Quantity(Quantity::new(3.0, QuantityType::Scalar)),
129            Value::Quantity(Quantity::new(3.0, QuantityType::Scalar)),
130            Value::Quantity(Quantity::new(3.0, QuantityType::Scalar)),
131        ]),
132        crate::ty::Type::Quantity(QuantityType::Scalar),
133    );
134
135    if let Value::Bool(result) = list
136        .call_method(
137            &"all_equal".into(),
138            &ArgumentValueList::default(),
139            &mut EvalContext::default(),
140        )
141        .expect("test error")
142    {
143        assert!(result);
144    } else {
145        panic!("Test failed");
146    }
147}