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