microcad_lang/eval/statements/
assignment_statement.rs

1// Copyright © 2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4use crate::eval::*;
5use crate::value::*;
6
7impl Assignment {
8    /// Check if the specified type matches the found type.
9    pub fn type_check(&self, found: Type) -> EvalResult<()> {
10        if let Some(ty) = &self.specified_type {
11            if ty.ty() != found {
12                return Err(EvalError::TypeMismatch {
13                    id: self.id.clone(),
14                    expected: ty.ty(),
15                    found,
16                });
17            }
18        }
19
20        Ok(())
21    }
22}
23
24impl Eval<()> for AssignmentStatement {
25    fn eval(&self, context: &mut EvalContext) -> EvalResult<()> {
26        log::debug!("Evaluating assignment statement:\n{self}");
27        context.grant(self)?;
28
29        let assignment = &self.assignment;
30
31        // evaluate assignment expression
32        let new_value: Value = assignment.expression.eval(context)?;
33        if let Err(err) = assignment.type_check(new_value.ty()) {
34            context.error(self, err)?;
35            return Ok(());
36        }
37
38        // apply any attributes to model value
39        let new_value = match new_value {
40            Value::Model(model) => {
41                let attributes = self.attribute_list.eval(context)?;
42                model.borrow_mut().attributes = attributes.clone();
43                Value::Model(model)
44            }
45            value => {
46                // all other values can't have attributes
47                if !self.attribute_list.is_empty() {
48                    context.error(
49                        &self.attribute_list,
50                        AttributeError::CannotAssignAttribute(
51                            self.assignment.expression.to_string(),
52                        ),
53                    )?;
54                }
55                value
56            }
57        };
58
59        let mut abort = false;
60
61        // lookup if we find any existing symbol
62        if let Ok(symbol) = context.lookup(&QualifiedName::from_id(assignment.id.clone())) {
63            let err = symbol.with_def_mut(|def| match def {
64                SymbolDefinition::Constant(_, id, value) => {
65                    if value.is_invalid() {
66                        *value = new_value.clone();
67                        None
68                    } else {
69                        Some((
70                            assignment.id.clone(),
71                            EvalError::ValueAlreadyDefined(
72                                id.clone(),
73                                value.to_string(),
74                                id.src_ref(),
75                            ),
76                        ))
77                    }
78                }
79                SymbolDefinition::ConstExpression(..) => {
80                    abort = true;
81                    None
82                }
83                _ => Some((
84                    assignment.id.clone(),
85                    EvalError::NotAnLValue(assignment.id.clone()),
86                )),
87            });
88            // because logging is blocked while `symbol.borrow_mut()` it must run outside the borrow
89            if let Some((id, err)) = err {
90                context.error(&id, err)?;
91            }
92        }
93
94        if !abort {
95            // now check what to do with the value
96            match assignment.qualifier() {
97                Qualifier::Const => {
98                    if context.get_property(&assignment.id).is_ok() {
99                        todo!("property with that name exists")
100                    }
101
102                    let symbol = context.lookup(&assignment.id.clone().into());
103                    match symbol {
104                        Ok(symbol) => {
105                            if let Err(err) = symbol.set_value(new_value) {
106                                context.error(self, err)?
107                            }
108                        }
109                        Err(err) => context.error(self, err)?,
110                    }
111                }
112                Qualifier::Value => {
113                    let result = if context.get_property(&assignment.id).is_ok() {
114                        if context.is_init() {
115                            context.init_property(assignment.id.clone(), new_value)
116                        } else {
117                            todo!("property with that name exists")
118                        }
119                    } else {
120                        context.set_local_value(assignment.id.clone(), new_value)
121                    };
122                    if let Err(err) = result {
123                        context.error(self, err)?;
124                    }
125                }
126                Qualifier::Prop => {
127                    if context.get_local_value(&assignment.id).is_ok() {
128                        todo!("local value with that name exists")
129                    }
130                    if let Err(err) = context.init_property(assignment.id.clone(), new_value) {
131                        context.error(self, err)?;
132                    }
133                }
134            }
135        }
136
137        Ok(())
138    }
139}