Skip to main content

microcad_lang/eval/
expression.rs

1// Copyright © 2024-2026 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4use crate::{eval::*, model::*};
5
6impl Eval for RangeFirst {
7    fn eval(&self, context: &mut EvalContext) -> EvalResult<Value> {
8        let value: Value = self.0.eval(context)?;
9        Ok(match value {
10            Value::Integer(_) => value,
11            value => {
12                context.error(
13                    self,
14                    EvalError::ExpectedType {
15                        expected: Type::Integer,
16                        found: value.ty(),
17                    },
18                )?;
19
20                Value::None
21            }
22        })
23    }
24}
25
26impl Eval for RangeLast {
27    fn eval(&self, context: &mut EvalContext) -> EvalResult<Value> {
28        let value: Value = self.0.eval(context)?;
29        Ok(match value {
30            Value::Integer(_) => value,
31            value => {
32                context.error(
33                    self,
34                    EvalError::ExpectedType {
35                        expected: Type::Integer,
36                        found: value.ty(),
37                    },
38                )?;
39
40                Value::None
41            }
42        })
43    }
44}
45
46impl Eval for RangeExpression {
47    fn eval(&self, context: &mut EvalContext) -> EvalResult<Value> {
48        Ok(
49            match (self.first.eval(context)?, self.last.eval(context)?) {
50                (Value::Integer(first), Value::Integer(last)) => {
51                    if first > last {
52                        context.error(self, EvalError::BadRange(first, last))?;
53                    }
54
55                    Value::Array(Array::from_values(
56                        (first..last + 1).map(Value::Integer).collect(),
57                        Type::Integer,
58                    ))
59                }
60                (_, _) => Value::None,
61            },
62        )
63    }
64}
65
66impl Eval for ArrayExpression {
67    fn eval(&self, context: &mut EvalContext) -> EvalResult<Value> {
68        match &self.inner {
69            ArrayExpressionInner::Range(range_expression) => range_expression.eval(context),
70            ArrayExpressionInner::List(expressions) => {
71                let value_list = ValueList::new(
72                    expressions
73                        .iter()
74                        .map(|expr| expr.eval(context))
75                        .collect::<Result<_, _>>()?,
76                );
77
78                match value_list.types().common_type() {
79                    Some(common_type) => {
80                        match Value::Array(Array::from_values(value_list, common_type)) * self.unit
81                        {
82                            Ok(value) => Ok(value),
83                            Err(err) => {
84                                context.error(self, err)?;
85                                Ok(Value::None)
86                            }
87                        }
88                    }
89                    None => {
90                        context.error(
91                            self,
92                            EvalError::ArrayElementsDifferentTypes(value_list.types()),
93                        )?;
94                        Ok(Value::None)
95                    }
96                }
97            }
98        }
99    }
100}
101
102impl Eval<Option<Symbol>> for QualifiedName {
103    fn eval(&self, context: &mut EvalContext) -> EvalResult<Option<Symbol>> {
104        match context.lookup(self, LookupTarget::AnyButMethod) {
105            Ok(symbol) => Ok(Some(symbol.clone())),
106            Err(error) => {
107                context.error(self, error)?;
108                Ok(None)
109            }
110        }
111    }
112}
113
114impl Eval for QualifiedName {
115    fn eval(&self, context: &mut EvalContext) -> EvalResult<Value> {
116        context
117            .lookup(self, LookupTarget::AnyButMethod)?
118            .with_def(|def| match def {
119                SymbolDef::Root => unreachable!("<ROOT> cannot be looked up"),
120                SymbolDef::Constant(.., value) | SymbolDef::Argument(_, value) => Ok(value.clone()),
121                SymbolDef::Assignment(a) => a.eval(context),
122                SymbolDef::SourceFile(_) => Ok(Value::None),
123
124                SymbolDef::Module(ns) => {
125                    context.error(self, EvalError::UnexpectedNested("mod", ns.id.clone()))?;
126                    Ok(Value::None)
127                }
128                SymbolDef::Workbench(w) => {
129                    context.error(
130                        self,
131                        EvalError::UnexpectedNested(w.kind.as_str(), w.id.clone()),
132                    )?;
133                    Ok(Value::None)
134                }
135                SymbolDef::Function(f) => {
136                    context.error(self, EvalError::UnexpectedNested("function", f.id.clone()))?;
137                    Ok(Value::None)
138                }
139                SymbolDef::Builtin(bm) => {
140                    context.error(self, EvalError::UnexpectedNested("builtin", bm.id.clone()))?;
141                    Ok(Value::None)
142                }
143                SymbolDef::Alias(_, id, _) => {
144                    // Alias should have been resolved within previous lookup()
145                    unreachable!(
146                        "Unexpected alias {id} in value expression at {}",
147                        self.src_ref()
148                    )
149                }
150                SymbolDef::UseAll(_, name) => {
151                    unreachable!("Unexpected use {name} in value expression")
152                }
153                #[cfg(test)]
154                SymbolDef::Tester(..) => {
155                    unreachable!()
156                }
157            })
158    }
159}
160
161impl Expression {
162    /// Evaluate an expression together with an attribute list.
163    ///
164    /// The attribute list will be also evaluated and the resulting attributes
165    /// will be assigned to the resulting value.
166    pub fn eval_with_attribute_list(
167        &self,
168        attribute_list: &AttributeList,
169        context: &mut EvalContext,
170    ) -> EvalResult<Value> {
171        let value = self.eval(context)?;
172        match value {
173            Value::Model(model) => {
174                let attributes = attribute_list.eval(context)?;
175                model.borrow_mut().attributes = attributes.clone();
176                Ok(Value::Model(model))
177            }
178            Value::None => Ok(Value::None),
179            _ => {
180                if !attribute_list.is_empty() {
181                    context.error(
182                        attribute_list,
183                        AttributeError::CannotAssignAttribute(self.to_string()),
184                    )?;
185                }
186                Ok(value)
187            }
188        }
189    }
190}
191
192impl Eval for Expression {
193    fn eval(&self, context: &mut EvalContext) -> EvalResult<Value> {
194        log::trace!("Evaluating expression:\n{self}");
195        let result = match self {
196            Self::Literal(literal) => literal.eval(context),
197            Self::FormatString(format_string) => format_string.eval(context),
198            Self::ArrayExpression(array_expression) => array_expression.eval(context),
199            Self::TupleExpression(tuple_expression) => tuple_expression.eval(context),
200            Self::BinaryOp {
201                lhs,
202                op,
203                rhs,
204                src_ref: _,
205            } => {
206                let lhs: Value = lhs.eval(context)?;
207                let rhs: Value = rhs.eval(context)?;
208                if lhs.is_invalid() || rhs.is_invalid() {
209                    return Ok(Value::None);
210                }
211
212                match Value::binary_op(lhs, rhs, op.as_str()) {
213                    Err(err) => {
214                        context.error(self, err)?;
215                        Ok(Value::None)
216                    }
217                    Ok(value) => Ok(value),
218                }
219            }
220            Self::UnaryOp {
221                op,
222                rhs,
223                src_ref: _,
224            } => {
225                let value: Value = rhs.eval(context)?;
226                value.unary_op(op.as_str()).map_err(EvalError::ValueError)
227            }
228            Self::ArrayElementAccess(lhs, rhs, _) => {
229                let lhs = lhs.eval(context)?;
230                let rhs = rhs.eval(context)?;
231
232                match (lhs, rhs) {
233                    (Value::Array(list), Value::Integer(index)) => {
234                        let index = index as usize;
235                        if index < list.len() {
236                            match list.get(index) {
237                                Some(value) => Ok(value.clone()),
238                                None => Err(EvalError::ListIndexOutOfBounds {
239                                    index,
240                                    len: list.len(),
241                                }),
242                            }
243                        } else {
244                            context.error(
245                                self,
246                                EvalError::ListIndexOutOfBounds {
247                                    index,
248                                    len: list.len(),
249                                },
250                            )?;
251                            Ok(Value::None)
252                        }
253                    }
254                    _ => unimplemented!(),
255                }
256            }
257            Self::MethodCall(lhs, method_call, _) => method_call.eval(context, lhs),
258            Self::Call(call) => call.eval(context),
259            Self::Body(body) => {
260                if let Some(model) = body.eval(context)? {
261                    Ok(model.into())
262                } else {
263                    Ok(Value::None)
264                }
265            }
266            Self::If(if_) => {
267                if let Some(model) = if_.eval(context)? {
268                    Ok(model.into())
269                } else {
270                    Ok(Value::None)
271                }
272            }
273            Self::QualifiedName(qualified_name) => qualified_name.eval(context),
274            Self::Marker(marker) => {
275                let model: Option<Model> = marker.eval(context)?;
276                Ok(model.map(Value::Model).unwrap_or_default())
277            }
278            // Access a property `x` of an expression `circle.x`
279            Self::PropertyAccess(lhs, id, src_ref) => {
280                let value: Value = lhs.eval(context)?;
281                match value {
282                    Value::Tuple(tuple) => match tuple.by_id(id) {
283                        Some(value) => return Ok(value.clone()),
284                        None => context.error(src_ref, EvalError::PropertyNotFound(id.clone()))?,
285                    },
286                    Value::Model(model) => match model.borrow().get_property(id) {
287                        Some(prop) => return Ok(prop.clone()),
288                        None => context.error(src_ref, EvalError::PropertyNotFound(id.clone()))?,
289                    },
290                    _ => {}
291                }
292
293                Ok(Value::None)
294            }
295            Self::AttributeAccess(lhs, identifier, src_ref) => {
296                let value: Value = lhs.eval(context)?;
297                let value = value.get_attribute_value(identifier);
298                if value == Value::None {
299                    context.error(src_ref, AttributeError::NotFound(identifier.clone()))?;
300                }
301                Ok(value)
302            }
303            expr => todo!("{expr:?}"),
304        };
305        match result {
306            Ok(value) => {
307                log::trace!("Evaluated expression:\n{self:?}\n--- into ---\n{value:?}");
308                Ok(value)
309            }
310            Err(err) => {
311                context.error(self, err)?;
312                Ok(Value::None)
313            }
314        }
315    }
316}
317
318impl Eval<Option<Model>> for Expression {
319    fn eval(&self, context: &mut EvalContext) -> EvalResult<Option<Model>> {
320        Ok(match self.eval(context)? {
321            Value::Model(model) => Some(model),
322            _ => None,
323        })
324    }
325}