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
153pub struct LuaExceptionProfile {
155 raised_value_class: ClassRef,
156}
157
158impl LuaExceptionProfile {
159 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 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 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}