Skip to main content

microcad_lang/eval/statements/
mod.rs

1// Copyright © 2025-2026 The µcad authors <info@microcad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! Workbench definition syntax element evaluation
5// Copyright © 2025 The µcad authors <info@microcad.xyz>
6// SPDX-License-Identifier: AGPL-3.0-or-later
7
8use microcad_lang_base::PushDiag;
9
10use crate::{eval::*, lower::ir, model::*, symbol::SymbolDef};
11
12mod assignment_statement;
13mod expression_statement;
14mod if_statement;
15mod marker;
16mod return_statement;
17
18impl Eval for ir::Statement {
19    fn eval(&self, context: &mut EvalContext) -> EvalResult<Value> {
20        match self {
21            Self::Assignment(a) => {
22                a.eval(context)?;
23                Ok(Value::None)
24            }
25            Self::If(i) => i.eval(context),
26            Self::Expression(e) => e.eval(context),
27            Self::Return(r) => r.eval(context),
28
29            Self::Workbench(..)
30            | Self::Module(..)
31            | Self::Function(..)
32            | Self::InnerAttribute(..)
33            | Self::InnerDocComment(..)
34            | Self::Use(..)
35            | Self::Init(..) => Ok(Value::None),
36        }
37    }
38}
39
40impl Eval<Option<Model>> for ir::Statement {
41    fn eval(&self, context: &mut EvalContext) -> EvalResult<Option<Model>> {
42        let model: Option<Model> = match self {
43            Self::Assignment(a) => {
44                a.eval(context)?;
45                None
46            }
47            Self::If(i) => i.eval(context)?,
48            Self::Expression(e) => e.eval(context)?,
49
50            Self::Workbench(..)
51            | Self::Module(..)
52            | Self::Function(..)
53            | Self::Use(..)
54            | Self::Init(..)
55            | Self::Return(..)
56            | Self::InnerAttribute(..)
57            | Self::InnerDocComment(..) => None,
58        };
59
60        if let Some(ref model) = model {
61            if model.deduce_output_type() == OutputType::InvalidMixed {
62                context.error(self, EvalError::CannotMixGeometry)?;
63            }
64        }
65
66        Ok(model)
67    }
68}
69
70impl Eval<Value> for ir::StatementList {
71    fn eval(&self, context: &mut EvalContext) -> EvalResult<Value> {
72        let mut result = Value::None;
73        for statement in self.iter() {
74            log::trace!("Evaluating statement: {statement}");
75            match statement.eval(context)? {
76                Value::Return(result) => {
77                    return Ok(Value::Return(result));
78                }
79                value => result = value,
80            }
81        }
82        Ok(result)
83    }
84}
85
86/// Parse inner attributes of a statement list.
87impl Eval<Attributes> for ir::StatementList {
88    fn eval(&self, context: &mut EvalContext) -> EvalResult<Attributes> {
89        let mut attributes = Vec::new();
90        for statement in self.iter() {
91            if let ir::Statement::InnerAttribute(attribute) = statement {
92                attributes.append(&mut attribute.eval(context)?);
93            }
94        }
95
96        Ok(Attributes(attributes))
97    }
98}
99
100impl Eval<Models> for ir::StatementList {
101    fn eval(&self, context: &mut EvalContext) -> EvalResult<Models> {
102        let mut models = Models::default();
103        let mut output_type = OutputType::NotDetermined;
104
105        // Check if we are in a workbench and try to get the workbench kind.
106        let kind = context
107            .current_symbol()
108            .map(|symbol| {
109                symbol.with_def(|def| match def {
110                    SymbolDef::Workbench(workbench_definition) => {
111                        let frame = context.stack.current_frame().expect("Some stack frame");
112                        match frame {
113                            StackFrame::Workbench(..) => Some(workbench_definition.kind.value),
114                            _ => None,
115                        }
116                    }
117                    SymbolDef::SourceFile(_) | SymbolDef::Builtin(_) => None,
118                    _ => unreachable!(),
119                })
120            })
121            .unwrap_or_default();
122
123        for statement in self.iter() {
124            if let Some(model) = statement.eval(context)? {
125                output_type = output_type.merge(&model.deduce_output_type());
126
127                // We are in a workbench. Check if the workbench kind matches the current output type.
128                if let Some(kind) = kind {
129                    let expected_output_type = kind.into();
130                    if expected_output_type != OutputType::NotDetermined
131                        && output_type != expected_output_type
132                    {
133                        context.error(
134                            statement,
135                            EvalError::WorkbenchInvalidOutput {
136                                kind,
137                                produced: output_type,
138                                expected: expected_output_type,
139                            },
140                        )?;
141                    }
142                }
143
144                if output_type == OutputType::InvalidMixed {
145                    context.error(statement, EvalError::CannotMixGeometry)?;
146                }
147                models.push(model);
148            }
149        }
150        Ok(models)
151    }
152}