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                                location: assignment.src_ref(),
77                                name: id.clone(),
78                                value: value.to_string(),
79                                previous_location: id.src_ref(),
80                            },
81                        ))
82                    }
83                }
84                SymbolDef::Assignment(..) => {
85                    abort = true;
86                    None
87                }
88                _ => Some((
89                    assignment.id.clone(),
90                    EvalError::NotAnLValue(assignment.id.clone()),
91                )),
92            });
93            // because logging is blocked while `symbol.borrow_mut()` it must run outside the borrow
94            if let Some((id, err)) = err {
95                context.error(&id, err)?;
96            }
97        }
98
99        if !abort {
100            // now check what to do with the value
101            match assignment.qualifier() {
102                Qualifier::Const => {
103                    if context.get_property(&assignment.id).is_ok() {
104                        todo!("property with that name exists")
105                    }
106
107                    let symbol = context.lookup(&assignment.id.clone().into(), LookupTarget::Value);
108                    match symbol {
109                        Ok(symbol) => {
110                            if let Err(err) = symbol.set_value(new_value) {
111                                context.error(self, err)?
112                            }
113                        }
114                        Err(err) => context.error(self, err)?,
115                    }
116                }
117                Qualifier::Value => {
118                    let result = if context.get_property(&assignment.id).is_ok() {
119                        if context.is_init() {
120                            context.init_property(assignment.id.clone(), new_value)
121                        } else {
122                            todo!("property with that name exists")
123                        }
124                    } else {
125                        context.set_local_value(assignment.id.clone(), new_value)
126                    };
127                    if let Err(err) = result {
128                        context.error(self, err)?;
129                    }
130                }
131                Qualifier::Prop => {
132                    if context.get_local_value(&assignment.id).is_ok() {
133                        todo!("local value with that name exists")
134                    }
135                    if let Err(err) = context.init_property(assignment.id.clone(), new_value) {
136                        context.error(self, err)?;
137                    }
138                }
139            }
140        }
141
142        Ok(())
143    }
144}
145
146impl Eval<Value> for Assignment {
147    fn eval(&self, context: &mut EvalContext) -> EvalResult<Value> {
148        self.expression.eval(context)
149    }
150}