Skip to main content

sim_lib_lang_lua/
call.rs

1use sim_kernel::{
2    Args, ClassId, ClassRef, CodecId, Cx, Origin, Result, SourceId, Span, Symbol, Value,
3};
4use sim_lib_control::{
5    BoundedSubclassOutcome, ClassMatchBudget, ClassMatchEvidence, ClassMatchOutcome,
6    ProtectedOutcome, Raised, match_raised_class, protected_call_with,
7};
8
9use crate::{
10    LuaEvalPolicy,
11    closure::{LuaClosure, call_lua_closure},
12    load::{LuaLoadFunction, LuaLoadedChunk, call_lua_loaded_chunk, run_lua_load_function},
13    stdlib_base::{LuaBaseFunction, run_lua_base_function},
14    stdlib_coroutine::{
15        LuaCoroutineFunction, LuaCoroutineWrapper, call_lua_coroutine_wrapper,
16        run_lua_coroutine_function,
17    },
18    stdlib_debug::{LuaDebugFunction, run_lua_debug_function},
19    stdlib_io::{LuaIoFunction, run_lua_io_function},
20    stdlib_math::{LuaMathFunction, run_lua_math_function},
21    stdlib_os::{LuaOsFunction, run_lua_os_function},
22    stdlib_package::{LuaPackageFunction, run_lua_package_function},
23    stdlib_string::{LuaStringFunction, run_lua_string_function},
24    stdlib_string_pattern::{LuaGMatchIterator, call_lua_gmatch_iterator},
25    stdlib_table::{LuaTableFunction, run_lua_table_function},
26    stdlib_utf8::{LuaUtf8Function, run_lua_utf8_function},
27};
28
29pub(crate) fn call_lua_value(
30    cx: &mut Cx,
31    policy: &LuaEvalPolicy,
32    callee: Value,
33    args: Vec<Value>,
34) -> Result<Vec<Value>> {
35    if let Some(closure) = callee.object().downcast_ref::<LuaClosure>() {
36        return call_lua_closure(cx, policy, closure, args);
37    }
38    if let Some(chunk) = callee.object().downcast_ref::<LuaLoadedChunk>() {
39        return call_lua_loaded_chunk(cx, chunk, args);
40    }
41    if let Some(function) = callee.object().downcast_ref::<LuaBaseFunction>() {
42        return run_lua_base_function(cx, policy, function.kind(), args);
43    }
44    if let Some(function) = callee.object().downcast_ref::<LuaLoadFunction>() {
45        return run_lua_load_function(cx, function.kind(), args);
46    }
47    if let Some(function) = callee.object().downcast_ref::<LuaCoroutineFunction>() {
48        return run_lua_coroutine_function(cx, policy, function.kind(), args);
49    }
50    if let Some(wrapper) = callee.object().downcast_ref::<LuaCoroutineWrapper>() {
51        return call_lua_coroutine_wrapper(cx, policy, wrapper, args);
52    }
53    if let Some(function) = callee.object().downcast_ref::<LuaTableFunction>() {
54        return run_lua_table_function(cx, policy, function.kind(), args);
55    }
56    if let Some(function) = callee.object().downcast_ref::<LuaMathFunction>() {
57        return run_lua_math_function(cx, policy, function.kind(), args);
58    }
59    if let Some(function) = callee.object().downcast_ref::<LuaPackageFunction>() {
60        return run_lua_package_function(cx, policy, function, args);
61    }
62    if let Some(function) = callee.object().downcast_ref::<LuaIoFunction>() {
63        return run_lua_io_function(cx, policy, function.kind(), args);
64    }
65    if let Some(function) = callee.object().downcast_ref::<LuaOsFunction>() {
66        return run_lua_os_function(cx, policy, function.kind(), args);
67    }
68    if let Some(function) = callee.object().downcast_ref::<LuaDebugFunction>() {
69        return run_lua_debug_function(cx, policy, function.kind(), args);
70    }
71    if let Some(function) = callee.object().downcast_ref::<LuaStringFunction>() {
72        return run_lua_string_function(cx, policy, function.kind(), args);
73    }
74    if let Some(iterator) = callee.object().downcast_ref::<LuaGMatchIterator>() {
75        return call_lua_gmatch_iterator(cx, policy, iterator);
76    }
77    if let Some(function) = callee.object().downcast_ref::<LuaUtf8Function>() {
78        return run_lua_utf8_function(cx, policy, function.kind(), args);
79    }
80    cx.call_value(callee, Args::new(args))
81        .map(|value| vec![value])
82}
83
84pub(crate) fn protected_lua_call(
85    cx: &mut Cx,
86    policy: &LuaEvalPolicy,
87    function: Value,
88    args: Vec<Value>,
89) -> Result<ProtectedOutcome<Raised>> {
90    let exceptions = LuaExceptionProfile::new(cx)?;
91    if function.object().downcast_ref::<LuaClosure>().is_some()
92        || function.object().downcast_ref::<LuaLoadedChunk>().is_some()
93        || function
94            .object()
95            .downcast_ref::<LuaBaseFunction>()
96            .is_some()
97        || function
98            .object()
99            .downcast_ref::<LuaLoadFunction>()
100            .is_some()
101        || function
102            .object()
103            .downcast_ref::<LuaCoroutineFunction>()
104            .is_some()
105        || function
106            .object()
107            .downcast_ref::<LuaCoroutineWrapper>()
108            .is_some()
109        || function
110            .object()
111            .downcast_ref::<LuaTableFunction>()
112            .is_some()
113        || function
114            .object()
115            .downcast_ref::<LuaMathFunction>()
116            .is_some()
117        || function
118            .object()
119            .downcast_ref::<LuaPackageFunction>()
120            .is_some()
121        || function.object().downcast_ref::<LuaIoFunction>().is_some()
122        || function.object().downcast_ref::<LuaOsFunction>().is_some()
123        || function
124            .object()
125            .downcast_ref::<LuaDebugFunction>()
126            .is_some()
127        || function
128            .object()
129            .downcast_ref::<LuaStringFunction>()
130            .is_some()
131        || function
132            .object()
133            .downcast_ref::<LuaGMatchIterator>()
134            .is_some()
135        || function
136            .object()
137            .downcast_ref::<LuaUtf8Function>()
138            .is_some()
139    {
140        return match call_lua_value(cx, policy, function, args) {
141            Ok(values) => Ok(ProtectedOutcome::Returned(values)),
142            Err(error) => Ok(ProtectedOutcome::Raised(
143                exceptions.raise(error_value(cx, error)?, lua_error_origin())?,
144            )),
145        };
146    }
147
148    protected_call_with(cx, function, Args::new(args), |cx, error| {
149        exceptions.raise(error_value(cx, error)?, lua_error_origin())
150    })
151}
152
153/// Lua's one adapter onto the shared exceptional-completion envelope.
154pub struct LuaExceptionProfile {
155    raised_value_class: ClassRef,
156}
157
158impl LuaExceptionProfile {
159    /// Builds the profile with Lua's canonical class for arbitrary raised values.
160    pub fn new(cx: &Cx) -> Result<Self> {
161        Ok(Self {
162            raised_value_class: cx.factory().class_stub(
163                ClassId(0x4c55_4101),
164                Symbol::qualified("lua", "RaisedValue"),
165            )?,
166        })
167    }
168
169    /// Wraps a Lua value without copying or replacing its managed identity.
170    pub fn raise(&self, value: Value, origin: Origin) -> Result<Raised> {
171        Raised::new(
172            self.raised_value_class.clone(),
173            value,
174            origin,
175            Symbol::qualified("lua", "raised-value"),
176        )
177    }
178
179    /// Lua matches only the canonical raised-value class; it adds no widening predicate.
180    pub fn matches(
181        &self,
182        cx: &mut Cx,
183        raised: &Raised,
184        candidate: ClassRef,
185        budget: ClassMatchBudget,
186    ) -> ClassMatchOutcome {
187        match_raised_class(
188            cx,
189            raised,
190            candidate,
191            budget,
192            |_, actual, expected, _| {
193                let raised = actual
194                    .object()
195                    .as_class()
196                    .expect("matcher validated class")
197                    .id();
198                let candidate = expected
199                    .object()
200                    .as_class()
201                    .expect("matcher validated class")
202                    .id();
203                let evidence = ClassMatchEvidence {
204                    raised,
205                    candidate,
206                    performed_work: 1,
207                };
208                if raised == candidate {
209                    BoundedSubclassOutcome::Subclass(evidence)
210                } else {
211                    BoundedSubclassOutcome::NotSubclass(evidence)
212                }
213            },
214            |_, _, _| Ok(true),
215        )
216    }
217}
218
219fn lua_error_origin() -> Origin {
220    Origin {
221        codec: CodecId(0),
222        source: SourceId("lua-protected-call".into()),
223        span: Span { start: 0, end: 0 },
224        trivia: Vec::new(),
225    }
226}
227
228pub(crate) fn error_value(cx: &mut Cx, error: sim_kernel::Error) -> Result<Value> {
229    cx.factory().string(error.to_string())
230}
231
232#[cfg(test)]
233mod tests {
234    use sim_kernel::{CodecId, Origin, SourceId, Span, testing::bare_cx};
235    use sim_lib_control::{ClassMatchBudget, ClassMatchOutcome};
236
237    use super::LuaExceptionProfile;
238    use crate::lua_table_from_values;
239
240    #[test]
241    fn raised_table_keeps_identity_and_uses_explicit_lua_match_policy() {
242        let mut cx = bare_cx();
243        let profile = LuaExceptionProfile::new(&cx).unwrap();
244        let table = lua_table_from_values(&mut cx, Vec::new()).unwrap();
245        let origin = Origin {
246            codec: CodecId(7),
247            source: SourceId("frozen-lua-capture".into()),
248            span: Span { start: 4, end: 9 },
249            trivia: Vec::new(),
250        };
251        let raised = profile.raise(table.clone(), origin).unwrap();
252
253        assert_eq!(raised.payload(), &table);
254        assert!(matches!(
255            profile.matches(
256                &mut cx,
257                &raised,
258                raised.class_ref().clone(),
259                ClassMatchBudget { work: 1 },
260            ),
261            ClassMatchOutcome::Matched(_)
262        ));
263    }
264}