Skip to main content

runmat_vm/object/
resolve.rs

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}