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 EvalContext,
20    ) -> EvalResult<T>;
21}
22
23impl CallMethod for Array {
24    fn call_method(
25        &self,
26        id: &QualifiedName,
27        _: &ArgumentValueList,
28        context: &mut EvalContext,
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            "head" => Ok(self.head()),
48            "tail" => Ok(Value::Array(self.tail())),
49            _ => {
50                context.error(id, EvalError::UnknownMethod(id.clone()))?;
51                Ok(Value::None)
52            }
53        }
54    }
55}
56
57impl CallMethod<Option<Model>> for Model {
58    fn call_method(
59        &self,
60        name: &QualifiedName,
61        args: &ArgumentValueList,
62        context: &mut EvalContext,
63    ) -> EvalResult<Option<Model>> {
64        match context.lookup(name, LookupTarget::Method) {
65            Ok(symbol) => context.scope(
66                StackFrame::Call {
67                    symbol: symbol.clone(),
68                    args: args.clone(),
69                    src_ref: SrcRef::merge(name, args),
70                },
71                |context| {
72                    symbol.with_def(|def| match def {
73                        SymbolDef::Workbench(workbench_definition) => {
74                            let model = workbench_definition.call(
75                                SrcRef::merge(name, args),
76                                symbol.clone(),
77                                args,
78                                context,
79                            )?;
80
81                            Ok::<_, EvalError>(Some(model.replace_input_placeholders(self)))
82                        }
83                        SymbolDef::Builtin(builtin) => match builtin.call(args, context)? {
84                            Value::Model(model) => Ok(Some(model.replace_input_placeholders(self))),
85                            value => panic!("Builtin call returned {value} but no models."),
86                        },
87                        _ => {
88                            context.error(name, EvalError::SymbolCannotBeCalled(name.clone()))?;
89                            Ok(None)
90                        }
91                    })
92                },
93            ),
94            Err(err) => {
95                context.error(name, err)?;
96                Ok(None)
97            }
98        }
99    }
100}
101
102impl CallMethod for Value {
103    fn call_method(
104        &self,
105        id: &QualifiedName,
106        args: &ArgumentValueList,
107        context: &mut EvalContext,
108    ) -> EvalResult<Value> {
109        match self {
110            Value::Integer(_) => eval_todo!(context, id, "call_method for Integer"),
111            Value::Quantity(_) => eval_todo!(context, id, "call_method for Quantity"),
112            Value::Bool(_) => eval_todo!(context, id, "call_method for Bool"),
113            Value::String(_) => eval_todo!(context, id, "call_method for String"),
114            Value::Tuple(_) => eval_todo!(context, id, "call_method for Tuple"),
115            Value::Matrix(_) => eval_todo!(context, id, "call_method for Matrix"),
116            Value::Array(list) => list.call_method(id, args, context),
117            Value::Model(model) => Ok(model
118                .call_method(id, args, context)?
119                .map(Value::Model)
120                .unwrap_or_default()),
121            _ => {
122                context.error(id, EvalError::UnknownMethod(id.clone()))?;
123                Ok(Value::None)
124            }
125        }
126    }
127}
128
129#[test]
130fn call_list_method() {
131    let list = Array::from_values(
132        ValueList::new(vec![
133            Value::Quantity(Quantity::new(3.0, QuantityType::Scalar)),
134            Value::Quantity(Quantity::new(3.0, QuantityType::Scalar)),
135            Value::Quantity(Quantity::new(3.0, QuantityType::Scalar)),
136        ]),
137        crate::ty::Type::Quantity(QuantityType::Scalar),
138    );
139
140    if let Value::Bool(result) = list
141        .call_method(
142            &"all_equal".into(),
143            &ArgumentValueList::default(),
144            &mut EvalContext::default(),
145        )
146        .expect("test error")
147    {
148        assert!(result);
149    } else {
150        panic!("Test failed");
151    }
152}