Skip to main content

runmat_runtime/call/
closures.rs

1use crate::call::identity::external_qualified_display_name;
2use crate::runtime_error::semantic_error;
3use crate::RuntimeError;
4use runmat_types::MemberAccess;
5use runmat_value::{Closure, Value};
6
7pub fn closure_value(function_name: String, captures: Vec<Value>) -> Value {
8    Value::Closure(Closure {
9        function_name,
10        bound_function: None,
11        captures,
12    })
13}
14
15pub fn semantic_closure_value(
16    function: runmat_types::FunctionId,
17    display_name: String,
18    captures: Vec<Value>,
19) -> Value {
20    Value::Closure(Closure {
21        function_name: display_name,
22        bound_function: Some(function.0),
23        captures,
24    })
25}
26
27pub fn caller_class_for_function(caller_function_name: Option<&str>) -> Option<String> {
28    let caller_function_name = caller_function_name?;
29    if let Some((class_name, method_name)) = caller_function_name.rsplit_once('.') {
30        if !class_name.is_empty() && !method_name.is_empty() {
31            return Some(class_name.to_string());
32        }
33    }
34    crate::class_registry::class_names()
35        .into_iter()
36        .find(|class_name| {
37            crate::class_registry::get_class(class_name).is_some_and(|class_def| {
38                class_def
39                    .methods
40                    .values()
41                    .any(|method| method.function_name == caller_function_name)
42            })
43        })
44}
45
46pub fn method_access_permitted(
47    owner: &str,
48    access: &MemberAccess,
49    caller_function_name: Option<&str>,
50) -> bool {
51    match access {
52        MemberAccess::Public => true,
53        MemberAccess::Private => {
54            caller_class_for_function(caller_function_name).as_deref() == Some(owner)
55        }
56        MemberAccess::Protected => {
57            caller_class_for_function(caller_function_name).is_some_and(|caller_class| {
58                crate::class_registry::is_class_or_subclass(&caller_class, owner)
59            })
60        }
61    }
62}
63
64pub fn resolve_method_semantic_function_id(
65    owner: &str,
66    method_name: &str,
67    function_name: &str,
68) -> Option<usize> {
69    let trimmed = function_name.trim();
70    if !trimmed.is_empty() {
71        if let Some(function) = crate::user_functions::resolve_semantic_function_by_name(trimmed) {
72            return Some(function);
73        }
74        if !trimmed.contains('.') {
75            let owner_qualified = format!("{owner}.{trimmed}");
76            if let Some(function) =
77                crate::user_functions::resolve_semantic_function_by_name(&owner_qualified)
78            {
79                return Some(function);
80            }
81        }
82    }
83    crate::user_functions::resolve_semantic_function_by_name(&format!("{owner}.{method_name}"))
84}
85
86pub fn load_method_closure(
87    base: Value,
88    name: String,
89    caller_function_name: Option<&str>,
90) -> Result<Value, RuntimeError> {
91    match base {
92        Value::Object(object) => {
93            let function_name = external_qualified_display_name(&object.class_name, &name);
94            Ok(Value::Closure(Closure {
95                bound_function: crate::user_functions::resolve_semantic_function_by_name(
96                    &function_name,
97                ),
98                function_name,
99                captures: vec![Value::Object(object)],
100            }))
101        }
102        Value::ClassRef(class_name) => {
103            if let Some((method, owner)) = crate::class_registry::lookup_method(&class_name, &name)
104            {
105                if !method.is_static {
106                    return Err(semantic_error(
107                        "MethodNotStatic",
108                        format!("Method '{name}' is not static"),
109                    ));
110                }
111                if !method_access_permitted(&owner, &method.access, caller_function_name) {
112                    return Err(semantic_error(
113                        "MethodPrivate",
114                        format!("Method '{name}' is private"),
115                    ));
116                }
117                return Ok(Value::Closure(Closure {
118                    bound_function: resolve_method_semantic_function_id(
119                        &owner,
120                        &name,
121                        &method.function_name,
122                    ),
123                    function_name: method.function_name,
124                    captures: vec![],
125                }));
126            }
127            let qualified_name = external_qualified_display_name(&class_name, &name);
128            if runmat_builtins::builtin_name_is_known(&qualified_name) {
129                Ok(Value::Closure(Closure {
130                    bound_function: crate::user_functions::resolve_semantic_function_by_name(
131                        &qualified_name,
132                    ),
133                    function_name: qualified_name,
134                    captures: vec![],
135                }))
136            } else {
137                Err(semantic_error(
138                    "UnknownStaticMethod",
139                    format!("Unknown static method '{name}' on class {class_name}"),
140                ))
141            }
142        }
143        _ => Err(semantic_error(
144            "LoadMethod",
145            "LoadMethod requires object or classref",
146        )),
147    }
148}