1use crate::bytecode::Value;
2use crate::embed::{EmbeddedBuilder, EmbeddedProgram};
3use crate::number::{LustFloat, LustInt};
4use crate::{LustError, Result};
5use std::ffi::{CStr, CString};
6use std::os::raw::c_char;
7use std::ptr;
8use std::slice;
9thread_local! {
10 static LAST_ERROR: std::cell::RefCell<Option<CString>> = std::cell::RefCell::new(None);
11}
12
13fn clear_last_error() {
14 LAST_ERROR.with(|slot| {
15 slot.borrow_mut().take();
16 });
17}
18
19fn set_last_error(message: impl Into<String>) {
20 let msg = message.into();
21 LAST_ERROR.with(|slot| {
22 let cstring = CString::new(msg.clone()).unwrap_or_else(|_| {
23 CString::new("FFI error message contained null byte").expect("static string")
24 });
25 *slot.borrow_mut() = Some(cstring);
26 });
27}
28
29fn handle_error(err: LustError) {
30 set_last_error(err.to_string());
31}
32
33fn handle_result<T>(result: Result<T>) -> Option<T> {
34 match result {
35 Ok(value) => Some(value),
36 Err(err) => {
37 handle_error(err);
38 None
39 }
40 }
41}
42
43#[repr(C)]
44#[allow(non_camel_case_types)]
45#[derive(Clone, Copy, Debug, PartialEq, Eq)]
46pub enum LustFfiValueTag {
47 LUST_FFI_VALUE_NIL = 0,
48 LUST_FFI_VALUE_BOOL = 1,
49 LUST_FFI_VALUE_INT = 2,
50 LUST_FFI_VALUE_FLOAT = 3,
51 LUST_FFI_VALUE_STRING = 4,
52}
53
54#[repr(C)]
55#[derive(Clone, Copy, Debug)]
56pub struct LustFfiValue {
57 pub tag: LustFfiValueTag,
58 pub bool_value: bool,
59 pub int_value: LustInt,
60 pub float_value: LustFloat,
61 pub string_ptr: *mut c_char,
62}
63
64impl Default for LustFfiValue {
65 fn default() -> Self {
66 Self {
67 tag: LustFfiValueTag::LUST_FFI_VALUE_NIL,
68 bool_value: false,
69 int_value: 0,
70 float_value: 0.0,
71 string_ptr: ptr::null_mut(),
72 }
73 }
74}
75
76fn value_from_ffi(value: &LustFfiValue) -> Result<Value> {
77 match value.tag {
78 LustFfiValueTag::LUST_FFI_VALUE_NIL => Ok(Value::Nil),
79 LustFfiValueTag::LUST_FFI_VALUE_BOOL => Ok(Value::Bool(value.bool_value)),
80 LustFfiValueTag::LUST_FFI_VALUE_INT => Ok(Value::Int(value.int_value)),
81 LustFfiValueTag::LUST_FFI_VALUE_FLOAT => Ok(Value::Float(value.float_value)),
82 LustFfiValueTag::LUST_FFI_VALUE_STRING => {
83 if value.string_ptr.is_null() {
84 return Err(LustError::RuntimeError {
85 message: "String argument pointer was null".into(),
86 });
87 }
88
89 let c_str = unsafe { CStr::from_ptr(value.string_ptr) };
90 let string = c_str.to_str().map_err(|_| LustError::RuntimeError {
91 message: "String argument contained invalid UTF-8".into(),
92 })?;
93 Ok(Value::String(std::rc::Rc::new(string.to_owned())))
94 }
95 }
96}
97
98fn value_to_ffi(value: Value) -> Result<LustFfiValue> {
99 match value {
100 Value::Nil => Ok(LustFfiValue::default()),
101 Value::Bool(flag) => Ok(LustFfiValue {
102 tag: LustFfiValueTag::LUST_FFI_VALUE_BOOL,
103 bool_value: flag,
104 ..Default::default()
105 }),
106 Value::Int(i) => Ok(LustFfiValue {
107 tag: LustFfiValueTag::LUST_FFI_VALUE_INT,
108 int_value: i,
109 ..Default::default()
110 }),
111 Value::Float(f) => Ok(LustFfiValue {
112 tag: LustFfiValueTag::LUST_FFI_VALUE_FLOAT,
113 float_value: f,
114 ..Default::default()
115 }),
116 Value::String(s) => {
117 let cstring = CString::new(s.as_str()).map_err(|_| LustError::RuntimeError {
118 message: "String result contained interior null byte".into(),
119 })?;
120 let ptr = cstring.into_raw();
121 Ok(LustFfiValue {
122 tag: LustFfiValueTag::LUST_FFI_VALUE_STRING,
123 string_ptr: ptr,
124 ..Default::default()
125 })
126 }
127
128 other => Err(LustError::RuntimeError {
129 message: format!("Unsupported Lust value for FFI conversion: {:?}", other),
130 }),
131 }
132}
133
134#[no_mangle]
135pub extern "C" fn lust_clear_last_error() {
136 clear_last_error();
137}
138
139#[no_mangle]
140pub extern "C" fn lust_last_error_message() -> *const c_char {
141 LAST_ERROR.with(|slot| {
142 if let Some(err) = slot.borrow().as_ref() {
143 err.as_ptr()
144 } else {
145 ptr::null()
146 }
147 })
148}
149
150#[no_mangle]
151pub extern "C" fn lust_string_free(ptr: *mut c_char) {
152 if ptr.is_null() {
153 return;
154 }
155
156 unsafe {
157 drop(CString::from_raw(ptr));
158 }
159}
160
161#[no_mangle]
162pub extern "C" fn lust_value_dispose(value: *mut LustFfiValue) {
163 if value.is_null() {
164 return;
165 }
166
167 unsafe {
168 if (*value).tag == LustFfiValueTag::LUST_FFI_VALUE_STRING && !(*value).string_ptr.is_null()
169 {
170 drop(CString::from_raw((*value).string_ptr));
171 }
172
173 *value = LustFfiValue::default();
174 }
175}
176
177#[no_mangle]
178pub extern "C" fn lust_builder_new() -> *mut EmbeddedBuilder {
179 clear_last_error();
180 Box::into_raw(Box::new(EmbeddedBuilder::new()))
181}
182
183#[no_mangle]
184pub extern "C" fn lust_builder_free(builder: *mut EmbeddedBuilder) {
185 if builder.is_null() {
186 return;
187 }
188
189 unsafe {
190 drop(Box::from_raw(builder));
191 }
192}
193
194#[no_mangle]
195pub extern "C" fn lust_builder_add_module(
196 builder: *mut EmbeddedBuilder,
197 module_path: *const c_char,
198 source: *const c_char,
199) -> bool {
200 clear_last_error();
201 if builder.is_null() {
202 set_last_error("Builder pointer was null");
203 return false;
204 }
205
206 if module_path.is_null() {
207 set_last_error("Module path pointer was null");
208 return false;
209 }
210
211 if source.is_null() {
212 set_last_error("Source pointer was null");
213 return false;
214 }
215
216 let path = unsafe { CStr::from_ptr(module_path) };
217 let source_str = unsafe { CStr::from_ptr(source) };
218 let path_str = match path.to_str() {
219 Ok(s) => s,
220 Err(_) => {
221 set_last_error("Module path was not valid UTF-8");
222 return false;
223 }
224 };
225 let source_str = match source_str.to_str() {
226 Ok(s) => s,
227 Err(_) => {
228 set_last_error("Source code was not valid UTF-8");
229 return false;
230 }
231 };
232 let builder_ref = unsafe { &mut *builder };
233 builder_ref.add_module(path_str, source_str);
234 true
235}
236
237#[no_mangle]
238pub extern "C" fn lust_builder_set_entry_module(
239 builder: *mut EmbeddedBuilder,
240 module_path: *const c_char,
241) -> bool {
242 clear_last_error();
243 if builder.is_null() {
244 set_last_error("Builder pointer was null");
245 return false;
246 }
247
248 if module_path.is_null() {
249 set_last_error("Module path pointer was null");
250 return false;
251 }
252
253 let path = unsafe { CStr::from_ptr(module_path) };
254 let path_str = match path.to_str() {
255 Ok(s) => s,
256 Err(_) => {
257 set_last_error("Module path was not valid UTF-8");
258 return false;
259 }
260 };
261 let builder_ref = unsafe { &mut *builder };
262 builder_ref.set_entry_module(path_str);
263 true
264}
265
266#[no_mangle]
267pub extern "C" fn lust_builder_set_base_dir(
268 builder: *mut EmbeddedBuilder,
269 base_dir: *const c_char,
270) -> bool {
271 clear_last_error();
272 if builder.is_null() {
273 set_last_error("Builder pointer was null");
274 return false;
275 }
276
277 if base_dir.is_null() {
278 set_last_error("Base directory pointer was null");
279 return false;
280 }
281
282 let base_dir = unsafe { CStr::from_ptr(base_dir) };
283 let base_dir_str = match base_dir.to_str() {
284 Ok(s) => s,
285 Err(_) => {
286 set_last_error("Base directory was not valid UTF-8");
287 return false;
288 }
289 };
290 let builder_ref = unsafe { &mut *builder };
291 builder_ref.set_entry_module(base_dir_str);
292 true
293}
294
295#[no_mangle]
296pub extern "C" fn lust_builder_compile(builder: *mut EmbeddedBuilder) -> *mut EmbeddedProgram {
297 clear_last_error();
298 if builder.is_null() {
299 set_last_error("Builder pointer was null");
300 return ptr::null_mut();
301 }
302
303 let builder_box = unsafe { Box::from_raw(builder) };
304 match builder_box.compile() {
305 Ok(program) => Box::into_raw(Box::new(program)),
306 Err(err) => {
307 handle_error(err);
308 ptr::null_mut()
309 }
310 }
311}
312
313#[no_mangle]
314pub extern "C" fn lust_program_free(program: *mut EmbeddedProgram) {
315 if program.is_null() {
316 return;
317 }
318
319 unsafe {
320 drop(Box::from_raw(program));
321 }
322}
323
324#[no_mangle]
325pub extern "C" fn lust_program_run_entry(program: *mut EmbeddedProgram) -> bool {
326 clear_last_error();
327 if program.is_null() {
328 set_last_error("Program pointer was null");
329 return false;
330 }
331
332 let program_ref = unsafe { &mut *program };
333 handle_result(program_ref.run_entry_script()).is_some()
334}
335
336#[no_mangle]
337pub extern "C" fn lust_program_call(
338 program: *mut EmbeddedProgram,
339 function_name: *const c_char,
340 args: *const LustFfiValue,
341 args_len: usize,
342 out_value: *mut LustFfiValue,
343) -> bool {
344 clear_last_error();
345 if program.is_null() {
346 set_last_error("Program pointer was null");
347 return false;
348 }
349
350 if function_name.is_null() {
351 set_last_error("Function name pointer was null");
352 return false;
353 }
354
355 if args_len > 0 && args.is_null() {
356 set_last_error("Arguments pointer was null while args_len > 0");
357 return false;
358 }
359
360 let func_name = unsafe { CStr::from_ptr(function_name) };
361 let func_name = match func_name.to_str() {
362 Ok(name) => name.to_owned(),
363 Err(_) => {
364 set_last_error("Function name was not valid UTF-8");
365 return false;
366 }
367 };
368 let arg_slice = if args_len == 0 {
369 &[][..]
370 } else {
371 unsafe { slice::from_raw_parts(args, args_len) }
372 };
373 let mut converted_args = Vec::with_capacity(arg_slice.len());
374 for arg in arg_slice {
375 match value_from_ffi(arg) {
376 Ok(value) => converted_args.push(value),
377 Err(err) => {
378 handle_error(err);
379 return false;
380 }
381 }
382 }
383
384 let program_ref = unsafe { &mut *program };
385 let result = match program_ref.call_raw(&func_name, converted_args) {
386 Ok(value) => value,
387 Err(err) => {
388 handle_error(err);
389 return false;
390 }
391 };
392 if out_value.is_null() {
393 return true;
394 }
395
396 match value_to_ffi(result) {
397 Ok(converted) => {
398 unsafe {
399 *out_value = converted;
400 }
401
402 true
403 }
404
405 Err(err) => {
406 handle_error(err);
407 false
408 }
409 }
410}
411
412#[no_mangle]
413pub extern "C" fn lust_program_get_global(
414 program: *mut EmbeddedProgram,
415 name: *const c_char,
416 out_value: *mut LustFfiValue,
417) -> bool {
418 clear_last_error();
419 if program.is_null() {
420 set_last_error("Program pointer was null");
421 return false;
422 }
423
424 if name.is_null() {
425 set_last_error("Global name pointer was null");
426 return false;
427 }
428
429 if out_value.is_null() {
430 set_last_error("Output value pointer was null");
431 return false;
432 }
433
434 let name_cstr = unsafe { CStr::from_ptr(name) };
435 let name_str = match name_cstr.to_str() {
436 Ok(s) => s,
437 Err(_) => {
438 set_last_error("Global name was not valid UTF-8");
439 return false;
440 }
441 };
442
443 let program_ref = unsafe { &mut *program };
444 let value = match program_ref.get_global_value(name_str) {
445 Some(value) => value,
446 None => {
447 set_last_error(format!("Global '{}' was not found", name_str));
448 return false;
449 }
450 };
451
452 match value_to_ffi(value) {
453 Ok(converted) => {
454 unsafe {
455 *out_value = converted;
456 }
457
458 true
459 }
460 Err(err) => {
461 handle_error(err);
462 false
463 }
464 }
465}
466
467#[no_mangle]
468pub extern "C" fn lust_program_set_global(
469 program: *mut EmbeddedProgram,
470 name: *const c_char,
471 value: *const LustFfiValue,
472) -> bool {
473 clear_last_error();
474 if program.is_null() {
475 set_last_error("Program pointer was null");
476 return false;
477 }
478
479 if name.is_null() {
480 set_last_error("Global name pointer was null");
481 return false;
482 }
483
484 if value.is_null() {
485 set_last_error("Value pointer was null");
486 return false;
487 }
488
489 let name_cstr = unsafe { CStr::from_ptr(name) };
490 let name_str = match name_cstr.to_str() {
491 Ok(s) => s,
492 Err(_) => {
493 set_last_error("Global name was not valid UTF-8");
494 return false;
495 }
496 };
497
498 let value_ref = unsafe { &*value };
499 let converted = match value_from_ffi(value_ref) {
500 Ok(val) => val,
501 Err(err) => {
502 handle_error(err);
503 return false;
504 }
505 };
506
507 let program_ref = unsafe { &mut *program };
508 program_ref.set_global_value(name_str, converted);
509 true
510}
511