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 Context) -> 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.clone().into(),
52                        ),
53                    )?;
54                }
55                value
56            }
57        };
58
59        // lookup if we find any existing symbol
60        if let Ok(symbol) = context.lookup(&QualifiedName::from_id(assignment.id.clone())) {
61            let err = match &mut symbol.borrow_mut().def {
62                SymbolDefinition::Constant(_, identifier, value) => {
63                    if value.is_invalid() {
64                        *value = new_value.clone();
65                        None
66                    } else {
67                        Some((
68                            assignment.id.clone(),
69                            EvalError::ValueAlreadyInitialized(
70                                identifier.clone(),
71                                value.clone(),
72                                identifier.src_ref(),
73                            ),
74                        ))
75                    }
76                }
77                _ => Some((
78                    assignment.id.clone(),
79                    EvalError::NotAnLValue(assignment.id.clone()),
80                )),
81            };
82            // because logging is blocked while `symbol.borrow_mut()` it must run outside the borrow
83            if let Some((id, err)) = err {
84                context.error(&id, err)?;
85            }
86        }
87
88        // now check what to do with the value
89        match assignment.qualifier {
90            Qualifier::Const => {
91                if context.get_property(&assignment.id).is_ok() {
92                    todo!("property with that name exists")
93                }
94
95                let symbol = context.lookup(&assignment.id.clone().into());
96                match symbol {
97                    Ok(symbol) => {
98                        if let Err(err) = symbol.set_value(new_value) {
99                            context.error(self, err)?
100                        }
101                    }
102                    Err(err) => context.error(self, err)?,
103                }
104            }
105            Qualifier::Value => {
106                let result = if context.get_property(&assignment.id).is_ok() {
107                    if context.is_init() {
108                        context.init_property(assignment.id.clone(), new_value)
109                    } else {
110                        todo!("property with that name exists")
111                    }
112                } else {
113                    context.set_local_value(assignment.id.clone(), new_value)
114                };
115                if let Err(err) = result {
116                    context.error(self, err)?;
117                }
118            }
119            Qualifier::Prop => {
120                if context.get_local_value(&assignment.id).is_ok() {
121                    todo!("local value with that name exists")
122                }
123                if let Err(err) = context.init_property(assignment.id.clone(), new_value) {
124                    context.error(self, err)?;
125                }
126            }
127        };
128
129        Ok(())
130    }
131}