microcad_lang/eval/call/
call_method.rs

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