Skip to main content

lust/
ffi.rs

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// #![cfg(feature = "std")]