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::*, model::*, symbol::SymbolDef};
11
12mod assignment_statement;
13mod expression_statement;
14mod if_statement;
15mod marker;
16mod return_statement;
17
18impl Eval for 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 Statement {
41    fn eval(&self, context: &mut EvalContext) -> EvalResult<Option<Model>> {
42        let model: Option<Model> = match self {
43            Self::Module(m) => {
44                m.eval(context)?;
45                None
46            }
47            Self::Assignment(a) => {
48                a.eval(context)?;
49                None
50            }
51            Self::If(i) => i.eval(context)?,
52            Self::Expression(e) => e.eval(context)?,
53
54            Self::Workbench(..)
55            | Self::Function(..)
56            | Self::Use(..)
57            | Self::Init(..)
58            | Self::Return(..)
59            | Self::InnerAttribute(..)
60            | Self::InnerDocComment(..) => None,
61        };
62
63        if let Some(ref model) = model {
64            if model.deduce_output_type() == OutputType::InvalidMixed {
65                context.error(self, EvalError::CannotMixGeometry)?;
66            }
67        }
68
69        Ok(model)
70    }
71}
72
73impl Eval<Value> for StatementList {
74    fn eval(&self, context: &mut EvalContext) -> EvalResult<Value> {
75        let mut result = Value::None;
76        for statement in self.iter() {
77            log::trace!("Evaluating statement: {statement}");
78            match statement.eval(context)? {
79                Value::Return(result) => {
80                    return Ok(Value::Return(result));
81                }
82                value => result = value,
83            }
84        }
85        Ok(result)
86    }
87}
88
89/// Parse inner attributes of a statement list.
90impl Eval<Attributes> for StatementList {
91    fn eval(&self, context: &mut EvalContext) -> EvalResult<Attributes> {
92        let mut attributes = Vec::new();
93        for statement in self.iter() {
94            if let Statement::InnerAttribute(attribute) = statement {
95                attributes.append(&mut attribute.eval(context)?);
96            }
97        }
98
99        Ok(Attributes(attributes))
100    }
101}
102
103impl Eval<Models> for StatementList {
104    fn eval(&self, context: &mut EvalContext) -> EvalResult<Models> {
105        let mut models = Models::default();
106        let mut output_type = OutputType::NotDetermined;
107
108        // Check if we are in a workbench and try to get the workbench kind.
109        let kind = context
110            .current_symbol()
111            .map(|symbol| {
112                symbol.with_def(|def| match def {
113                    SymbolDef::Workbench(workbench_definition) => {
114                        let frame = context.stack.current_frame().expect("Some stack frame");
115                        match frame {
116                            StackFrame::Workbench(..) => Some(workbench_definition.kind.value),
117                            _ => None,
118                        }
119                    }
120                    SymbolDef::SourceFile(_) | SymbolDef::Builtin(_) => None,
121                    _ => unreachable!(),
122                })
123            })
124            .unwrap_or_default();
125
126        for statement in self.iter() {
127            if let Some(model) = statement.eval(context)? {
128                output_type = output_type.merge(&model.deduce_output_type());
129
130                // We are in a workbench. Check if the workbench kind matches the current output type.
131                if let Some(kind) = kind {
132                    let expected_output_type = match kind {
133                        WorkbenchKind::Part => OutputType::Geometry3D,
134                        WorkbenchKind::Sketch => OutputType::Geometry2D,
135                        WorkbenchKind::Operation => OutputType::NotDetermined,
136                    };
137
138                    if expected_output_type != OutputType::NotDetermined
139                        && output_type != expected_output_type
140                    {
141                        context.error(
142                            statement,
143                            EvalError::WorkbenchInvalidOutput {
144                                kind,
145                                produced: output_type,
146                                expected: expected_output_type,
147                            },
148                        )?;
149                    }
150                }
151
152                if output_type == OutputType::InvalidMixed {
153                    context.error(statement, EvalError::CannotMixGeometry)?;
154                }
155                models.push(model);
156            }
157        }
158        Ok(models)
159    }
160}