microcad_lang/eval/call/
mod.rs

1// Copyright © 2024-2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! µcad Call related evaluation entities
5
6#[macro_use]
7mod argument;
8mod call_method;
9mod call_trait;
10
11pub use call_method::*;
12pub use call_trait::*;
13
14use crate::{eval::*, syntax::*, value::*};
15
16use thiserror::Error;
17
18impl Eval<ArgumentValueList> for ArgumentList {
19    /// Evaluate into a [`ArgumentValueList`].
20    fn eval(&self, context: &mut EvalContext) -> EvalResult<ArgumentValueList> {
21        self.iter()
22            .map(|arg| {
23                (
24                    arg.id.clone().unwrap_or(Identifier::none()),
25                    arg.eval(context),
26                )
27            })
28            .map(|(id, arg)| match arg {
29                Ok(arg) => Ok((id.clone(), arg)),
30                Err(err) => Err(err),
31            })
32            .collect()
33    }
34}
35
36/// Alternative evaluation type.
37///
38/// Used to prevent the evaluation of `QualifiedName`.
39/// `assert_valid()` and `assert_invalid()` need these untouched.
40pub struct ArgumentValueListRaw(ArgumentValueList);
41
42impl From<ArgumentValueListRaw> for ArgumentValueList {
43    fn from(value: ArgumentValueListRaw) -> Self {
44        value.0
45    }
46}
47
48impl Eval<ArgumentValueListRaw> for ArgumentList {
49    /// Evaluate into a [`ArgumentValueList`].
50    fn eval(&self, context: &mut EvalContext) -> EvalResult<ArgumentValueListRaw> {
51        let arguments = self
52            .iter()
53            .map(|arg| {
54                (
55                    arg.id.clone().unwrap_or(Identifier::none()),
56                    if let Expression::QualifiedName(name) = &arg.expression {
57                        Ok(ArgumentValue::new(
58                            Value::Target(Target::new(
59                                name.un_super(),
60                                match context.lookup(name, LookupTarget::Any) {
61                                    Ok(symbol) => Some(symbol.full_name()),
62                                    Err(_) => None,
63                                },
64                            )),
65                            arg.id.clone(),
66                            arg.src_ref.clone(),
67                        ))
68                    } else {
69                        arg.eval(context)
70                    },
71                )
72            })
73            .map(|(id, arg)| match arg {
74                Ok(arg) => Ok((id.clone(), arg)),
75                Err(err) => Err(err),
76            })
77            .collect::<EvalResult<_>>()?;
78
79        Ok(ArgumentValueListRaw(arguments))
80    }
81}
82
83impl Eval for Call {
84    fn eval(&self, context: &mut EvalContext) -> EvalResult<Value> {
85        // find self in symbol table by own name
86        let symbol = match context.lookup(&self.name, LookupTarget::Function) {
87            Ok(symbol) => symbol,
88            Err(err) => {
89                context.error(self, err)?;
90                return Ok(Value::None);
91            }
92        };
93
94        // evaluate arguments
95        let args: ArgumentValueList = if symbol.is_target_mode() {
96            // for assert_valid() and assert_invalid()
97            Eval::<ArgumentValueListRaw>::eval(&self.argument_list, context)?.into()
98        } else {
99            self.argument_list.eval(context)?
100        };
101
102        log::debug!(
103            "{call} {name:?}({args:?})",
104            name = self.name,
105            call = crate::mark!(CALL),
106        );
107
108        match context.scope(
109            StackFrame::Call {
110                symbol: symbol.clone(),
111                args: args.clone(),
112                src_ref: self.src_ref(),
113            },
114            |context| {
115                symbol.with_def(|def| match def {
116                    SymbolDef::Builtin(f) => f.call(&args, context),
117                    SymbolDef::Workbench(w) => {
118                        if matches!(*w.kind, WorkbenchKind::Operation) {
119                            context.error(self, EvalError::CannotCallOperationWithoutWorkpiece)?;
120                            Ok(Value::None)
121                        } else {
122                            Ok(Value::Model(w.call(
123                                self.src_ref(),
124                                symbol.clone(),
125                                &args,
126                                context,
127                            )?))
128                        }
129                    }
130                    SymbolDef::Function(f) => f.call(&args, context),
131                    _ => {
132                        context.error(self, EvalError::SymbolCannotBeCalled(symbol.full_name()))?;
133                        Ok(Value::None)
134                    }
135                })
136            },
137        ) {
138            Ok(value) => Ok(value),
139            Err(err) => {
140                context.error(self, err)?;
141                Ok(Value::None)
142            }
143        }
144    }
145}
146
147/// An error that occurred when looking for matching arguments between a call and a parameter definition.
148#[derive(Error, Debug)]
149pub enum MatchError {
150    /// Duplicated argument.
151    #[error("Duplicated argument: {0}")]
152    DuplicatedArgument(Identifier),
153    /// Occurs when a parameter was given in a call but not in the definition.
154    #[error("Parameter `{0}` is not defined.")]
155    ParameterNotDefined(Identifier),
156    /// Mismatching type.
157    #[error("Type mismatch for parameter `{0}`: expected `{1}`, got {2}")]
158    PositionalArgumentTypeMismatch(Identifier, Type, Type),
159    /// Parameter required by definition but given in the call.
160    #[error("Missing parameter: {0}")]
161    MissingParameter(Identifier),
162}