Skip to main content

microcad_lang/eval/
expression.rs

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