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        self.grant(context)?;
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                model.set_id(assignment.id.clone());
42                let attributes = self.attribute_list.eval(context)?;
43                model.borrow_mut().attributes = attributes.clone();
44                Value::Model(model)
45            }
46            value => {
47                // all other values can't have attributes
48                if !self.attribute_list.is_empty() {
49                    context.error(
50                        &self.attribute_list,
51                        AttributeError::CannotAssignAttribute(
52                            self.assignment.expression.to_string(),
53                        ),
54                    )?;
55                }
56                value
57            }
58        };
59
60        let mut abort = false;
61
62        // lookup if we find any existing symbol
63        if let Ok(symbol) = context.lookup(
64            &QualifiedName::from_id(assignment.id.clone()),
65            LookupTarget::Value,
66        ) {
67            let err = symbol.with_def_mut(|def| match def {
68                SymbolDef::Constant(_, id, value) => {
69                    if value.is_invalid() {
70                        *value = new_value.clone();
71                        None
72                    } else {
73                        Some((
74                            assignment.id.clone(),
75                            EvalError::ValueAlreadyDefined(
76                                id.clone(),
77                                value.to_string(),
78                                id.src_ref(),
79                            ),
80                        ))
81                    }
82                }
83                SymbolDef::Assignment(..) => {
84                    abort = true;
85                    None
86                }
87                _ => Some((
88                    assignment.id.clone(),
89                    EvalError::NotAnLValue(assignment.id.clone()),
90                )),
91            });
92            // because logging is blocked while `symbol.borrow_mut()` it must run outside the borrow
93            if let Some((id, err)) = err {
94                context.error(&id, err)?;
95            }
96        }
97
98        if !abort {
99            // now check what to do with the value
100            match assignment.qualifier() {
101                Qualifier::Const => {
102                    if context.get_property(&assignment.id).is_ok() {
103                        todo!("property with that name exists")
104                    }
105
106                    let symbol = context.lookup(&assignment.id.clone().into(), LookupTarget::Value);
107                    match symbol {
108                        Ok(symbol) => {
109                            if let Err(err) = symbol.set_value(new_value) {
110                                context.error(self, err)?
111                            }
112                        }
113                        Err(err) => context.error(self, err)?,
114                    }
115                }
116                Qualifier::Value => {
117                    let result = if context.get_property(&assignment.id).is_ok() {
118                        if context.is_init() {
119                            context.init_property(assignment.id.clone(), new_value)
120                        } else {
121                            todo!("property with that name exists")
122                        }
123                    } else {
124                        context.set_local_value(assignment.id.clone(), new_value)
125                    };
126                    if let Err(err) = result {
127                        context.error(self, err)?;
128                    }
129                }
130                Qualifier::Prop => {
131                    if context.get_local_value(&assignment.id).is_ok() {
132                        todo!("local value with that name exists")
133                    }
134                    if let Err(err) = context.init_property(assignment.id.clone(), new_value) {
135                        context.error(self, err)?;
136                    }
137                }
138            }
139        }
140
141        Ok(())
142    }
143}
144
145impl Eval<Value> for Assignment {
146    fn eval(&self, context: &mut EvalContext) -> EvalResult<Value> {
147        self.expression.eval(context)
148    }
149}