1use crate::interpreter::errors::mex;
2use runmat_builtins::{self, Access, Closure, StructValue, Value};
3use runmat_runtime::RuntimeError;
4
5pub async fn load_member(
6 base: Value,
7 field: String,
8 allow_init: bool,
9) -> Result<Value, RuntimeError> {
10 match base {
11 Value::Object(obj) => {
12 if let Some((p, _owner)) = runmat_builtins::lookup_property(&obj.class_name, &field) {
13 if p.is_static {
14 return Err(format!(
15 "Property '{}' is static; use classref('{}').{}",
16 field, obj.class_name, field
17 )
18 .into());
19 }
20 if p.get_access == Access::Private {
21 return Err(format!("Property '{}' is private", field).into());
22 }
23 if p.is_dependent {
24 let getter = format!("get.{field}");
25 if let Ok(v) =
26 runmat_runtime::call_builtin_async(&getter, &[Value::Object(obj.clone())])
27 .await
28 {
29 return Ok(v);
30 }
31 }
32 }
33 if let Some(v) = obj.properties.get(&field) {
34 Ok(v.clone())
35 } else if let Some((p2, _)) = runmat_builtins::lookup_property(&obj.class_name, &field)
36 {
37 if p2.is_dependent {
38 let backing = format!("{field}_backing");
39 if let Some(vb) = obj.properties.get(&backing) {
40 return Ok(vb.clone());
41 }
42 }
43 Err(format!(
44 "Undefined property '{}' for class {}",
45 field, obj.class_name
46 )
47 .into())
48 } else if let Some(cls) = runmat_builtins::get_class(&obj.class_name) {
49 if cls.methods.contains_key("subsref") {
50 let args = vec![
51 Value::Object(obj),
52 Value::String("subsref".to_string()),
53 Value::String(".".to_string()),
54 Value::String(field),
55 ];
56 runmat_runtime::call_builtin_async("call_method", &args).await
57 } else {
58 Err(format!(
59 "Undefined property '{}' for class {}",
60 field, obj.class_name
61 )
62 .into())
63 }
64 } else {
65 Err(format!("Unknown class {}", obj.class_name).into())
66 }
67 }
68 Value::HandleObject(handle) => {
69 let args = vec![
70 Value::HandleObject(handle),
71 Value::String("subsref".to_string()),
72 Value::String(".".to_string()),
73 Value::String(field),
74 ];
75 runmat_runtime::call_builtin_async("call_method", &args).await
76 }
77 Value::ClassRef(cls) => load_static_member(&cls, &field),
78 Value::Struct(st) => {
79 if let Some(v) = st.fields.get(&field) {
80 Ok(v.clone())
81 } else if allow_init {
82 Ok(Value::Struct(StructValue::new()))
83 } else {
84 Err(format!("Undefined field '{}'", field).into())
85 }
86 }
87 Value::Cell(ca) => {
88 let mut out: Vec<Value> = Vec::with_capacity(ca.data.len());
89 for v in &ca.data {
90 match &**v {
91 Value::Struct(st) => {
92 if let Some(fv) = st.fields.get(&field) {
93 out.push(fv.clone());
94 } else {
95 out.push(Value::Num(0.0));
96 }
97 }
98 other => out.push(other.clone()),
99 }
100 }
101 let new_cell = runmat_builtins::CellArray::new(out, ca.rows, ca.cols)
102 .map_err(|e| format!("cell field gather: {e}"))?;
103 Ok(Value::Cell(new_cell))
104 }
105 Value::MException(mexn) => {
106 let value = match field.as_str() {
107 "identifier" => Value::String(mexn.identifier.clone()),
108 "message" => Value::String(mexn.message.clone()),
109 "stack" => {
110 let values: Vec<Value> = mexn
111 .stack
112 .iter()
113 .map(|s| Value::String(s.clone()))
114 .collect();
115 let rows = values.len();
116 let cell = runmat_builtins::CellArray::new(values, rows, 1)
117 .map_err(|e| format!("MException.stack: {e}"))?;
118 Value::Cell(cell)
119 }
120 other => return Err(format!("Reference to non-existent field '{}'.", other).into()),
121 };
122 Ok(value)
123 }
124 _ => Err(mex("LoadMember", "LoadMember on non-object")),
125 }
126}
127
128pub async fn load_member_dynamic(
129 base: Value,
130 name: String,
131 allow_init: bool,
132) -> Result<Value, RuntimeError> {
133 load_member(base, name, allow_init).await
134}
135
136pub fn load_static_member(cls: &str, field: &str) -> Result<Value, RuntimeError> {
137 if let Some((p, owner)) = runmat_builtins::lookup_property(cls, field) {
138 if !p.is_static {
139 return Err(format!("Property '{}' is not static", field).into());
140 }
141 if p.get_access == Access::Private {
142 return Err(format!("Property '{}' is private", field).into());
143 }
144 if let Some(v) = runmat_builtins::get_static_property_value(&owner, field) {
145 Ok(v)
146 } else if let Some(v) = &p.default_value {
147 Ok(v.clone())
148 } else {
149 Ok(Value::Num(0.0))
150 }
151 } else if let Some((m, _owner)) = runmat_builtins::lookup_method(cls, field) {
152 if !m.is_static {
153 return Err(format!("Method '{}' is not static", field).into());
154 }
155 Ok(Value::Closure(Closure {
156 function_name: m.function_name,
157 captures: vec![],
158 }))
159 } else {
160 let qualified = format!("{cls}.{field}");
161 if runmat_builtins::builtin_functions()
162 .iter()
163 .any(|b| b.name == qualified)
164 {
165 Ok(Value::Closure(Closure {
166 function_name: qualified,
167 captures: vec![],
168 }))
169 } else {
170 Err(format!("Unknown property '{}' on class {}", field, cls).into())
171 }
172 }
173}
174
175pub async fn store_member<OnWrite>(
176 base: Value,
177 field: String,
178 rhs: Value,
179 allow_init: bool,
180 mut on_write: OnWrite,
181) -> Result<Value, RuntimeError>
182where
183 OnWrite: FnMut(&Value, &Value),
184{
185 match base {
186 Value::Object(mut obj) => {
187 if let Some((p, _owner)) = runmat_builtins::lookup_property(&obj.class_name, &field) {
188 if p.is_static {
189 return Err(format!(
190 "Property '{}' is static; use classref('{}').{}",
191 field, obj.class_name, field
192 )
193 .into());
194 }
195 if p.set_access == Access::Private {
196 return Err(format!("Property '{}' is private", field).into());
197 }
198 if p.is_dependent {
199 let setter = format!("set.{field}");
200 if let Ok(v) = runmat_runtime::call_builtin_async(
201 &setter,
202 &[Value::Object(obj.clone()), rhs.clone()],
203 )
204 .await
205 {
206 return Ok(v);
207 }
208 }
209 if let Some(oldv) = obj.properties.get(&field) {
210 on_write(oldv, &rhs);
211 }
212 obj.properties.insert(field, rhs);
213 Ok(Value::Object(obj))
214 } else if let Some(cls) = runmat_builtins::get_class(&obj.class_name) {
215 if cls.methods.contains_key("subsasgn") {
216 let args = vec![
217 Value::Object(obj),
218 Value::String("subsasgn".to_string()),
219 Value::String(".".to_string()),
220 Value::String(field),
221 rhs,
222 ];
223 runmat_runtime::call_builtin_async("call_method", &args).await
224 } else {
225 Err(format!("Undefined property '{}' for class {}", field, cls.name).into())
226 }
227 } else {
228 Err(format!("Unknown class {}", obj.class_name).into())
229 }
230 }
231 Value::ClassRef(cls) => {
232 if let Some((p, owner)) = runmat_builtins::lookup_property(&cls, &field) {
233 if !p.is_static {
234 return Err(format!("Property '{}' is not static", field).into());
235 }
236 if p.set_access == Access::Private {
237 return Err(format!("Property '{}' is private", field).into());
238 }
239 runmat_builtins::set_static_property_value_in_owner(&owner, &field, rhs)?;
240 Ok(Value::ClassRef(cls))
241 } else {
242 Err(format!("Unknown property '{}' on class {}", field, cls).into())
243 }
244 }
245 Value::HandleObject(handle) => {
246 let args = vec![
247 Value::HandleObject(handle),
248 Value::String("subsasgn".to_string()),
249 Value::String(".".to_string()),
250 Value::String(field),
251 rhs,
252 ];
253 runmat_runtime::call_builtin_async("call_method", &args).await
254 }
255 Value::Struct(mut st) => {
256 if let Some(oldv) = st.fields.get(&field) {
257 on_write(oldv, &rhs);
258 }
259 st.fields.insert(field, rhs);
260 Ok(Value::Struct(st))
261 }
262 Value::Cell(mut ca) => {
263 let rhs_cell = if let Value::Cell(rc) = &rhs {
264 Some(rc)
265 } else {
266 None
267 };
268 if let Some(rc) = rhs_cell {
269 if rc.rows != ca.rows || rc.cols != ca.cols {
270 return Err("Field assignment: cell rhs shape mismatch"
271 .to_string()
272 .into());
273 }
274 }
275 for i in 0..ca.data.len() {
276 let rv = if let Some(rc) = rhs_cell {
277 (*rc.data[i]).clone()
278 } else {
279 rhs.clone()
280 };
281 match &mut *ca.data[i] {
282 Value::Struct(st) => {
283 if let Some(oldv) = st.fields.get(&field) {
284 on_write(oldv, &rv);
285 }
286 st.fields.insert(field.clone(), rv);
287 }
288 other => {
289 let mut st = StructValue::new();
290 st.fields.insert(field.clone(), rv);
291 *other = Value::Struct(st);
292 }
293 }
294 }
295 Ok(Value::Cell(ca))
296 }
297 Value::Num(0.0) if allow_init => {
298 let mut st = StructValue::new();
299 st.fields.insert(field, rhs);
300 Ok(Value::Struct(st))
301 }
302 _ => Err(mex("StoreMember", "StoreMember on non-object")),
303 }
304}
305
306pub async fn store_member_dynamic<OnWrite>(
307 base: Value,
308 name: String,
309 rhs: Value,
310 allow_init: bool,
311 on_write: OnWrite,
312) -> Result<Value, RuntimeError>
313where
314 OnWrite: FnMut(&Value, &Value),
315{
316 store_member(base, name, rhs, allow_init, on_write).await
317}