gluon_c_api/
lib.rs

1//! A (WIP) C API allowing use of gluon in other langauges than Rust.
2#![doc(html_root_url = "https://docs.rs/gluon_c-api/0.18.2")] // # GLUON
3
4use std::{slice, str};
5
6use futures::{executor::block_on, future};
7
8use gluon::{
9    vm::{
10        api::{CPrimitive, Getable, Hole, OpaqueValue, Pushable},
11        stack,
12        thread::{RootedThread, Status, Thread, ThreadInternal},
13        types::{VmIndex, VmInt},
14    },
15    ThreadExt,
16};
17
18pub type Function = extern "C" fn(&Thread) -> Status;
19
20// TODO What should the c api return as errors
21// TODO How should error messages be returned
22#[repr(C)]
23#[derive(Debug, PartialEq)]
24pub enum Error {
25    Ok,
26    Unknown,
27}
28
29#[no_mangle]
30pub extern "C" fn glu_new_vm() -> *const Thread {
31    let vm = RootedThread::new();
32    vm.into_raw()
33}
34
35#[no_mangle]
36pub unsafe extern "C" fn glu_free_vm(vm: &Thread) {
37    RootedThread::from_raw(vm);
38}
39
40#[no_mangle]
41pub unsafe extern "C" fn glu_run_expr(
42    vm: &Thread,
43    module: &u8,
44    module_len: usize,
45    expr: &u8,
46    expr_len: usize,
47) -> Error {
48    let module = match str::from_utf8(slice::from_raw_parts(module, module_len)) {
49        Ok(s) => s,
50        Err(_) => return Error::Unknown,
51    };
52    let expr = match str::from_utf8(slice::from_raw_parts(expr, expr_len)) {
53        Ok(s) => s,
54        Err(_) => return Error::Unknown,
55    };
56    let result = vm.run_expr::<OpaqueValue<&Thread, Hole>>(module, expr);
57    match result {
58        Ok(_) => Error::Ok,
59        Err(_) => Error::Unknown,
60    }
61}
62
63#[no_mangle]
64pub unsafe extern "C" fn glu_load_script(
65    vm: &Thread,
66    module: &u8,
67    module_len: usize,
68    expr: &u8,
69    expr_len: usize,
70) -> Error {
71    let module = match str::from_utf8(slice::from_raw_parts(module, module_len)) {
72        Ok(s) => s,
73        Err(_) => return Error::Unknown,
74    };
75    let expr = match str::from_utf8(slice::from_raw_parts(expr, expr_len)) {
76        Ok(s) => s,
77        Err(_) => return Error::Unknown,
78    };
79    let result = vm.load_script(module, expr);
80    match result {
81        Ok(_) => Error::Ok,
82        Err(_) => Error::Unknown,
83    }
84}
85
86#[no_mangle]
87pub extern "C" fn glu_call_function(thread: &Thread, args: VmIndex) -> Error {
88    match block_on(future::poll_fn(|cx| {
89        let context = thread.context();
90        thread.call_function(cx, context, args)
91    })) {
92        Ok(_) => Error::Ok,
93        Err(_) => Error::Unknown,
94    }
95}
96
97#[no_mangle]
98pub extern "C" fn glu_len(vm: &Thread) -> usize {
99    let mut context = vm.context();
100    let stack = context.stack_frame::<stack::State>();
101    stack.len() as usize
102}
103
104#[no_mangle]
105pub extern "C" fn glu_pop(vm: &Thread, n: usize) {
106    let mut context = vm.context();
107    let mut stack = context.stack_frame::<stack::State>();
108    for _ in 0..n {
109        stack.pop();
110    }
111}
112
113#[no_mangle]
114pub extern "C" fn glu_push_int(vm: &Thread, int: VmInt) {
115    Thread::push(vm, int).unwrap();
116}
117
118#[no_mangle]
119pub extern "C" fn glu_push_byte(vm: &Thread, b: u8) {
120    Thread::push(vm, b).unwrap();
121}
122
123#[no_mangle]
124pub extern "C" fn glu_push_float(vm: &Thread, float: f64) {
125    Thread::push(vm, float).unwrap();
126}
127
128#[no_mangle]
129pub extern "C" fn glu_push_bool(vm: &Thread, b: i8) {
130    Thread::push(vm, b != 0).unwrap();
131}
132
133#[no_mangle]
134pub unsafe extern "C" fn glu_push_function(
135    vm: &Thread,
136    name: &u8,
137    len: usize,
138    function: Function,
139    args: VmIndex,
140) -> Error {
141    let s = match str::from_utf8(slice::from_raw_parts(name, len)) {
142        Ok(s) => s,
143        Err(_) => return Error::Unknown,
144    };
145    match Thread::push(vm, CPrimitive::new(function, args, s)) {
146        Ok(()) => Error::Ok,
147        Err(_) => Error::Unknown,
148    }
149}
150
151/// Push a string to the stack. The string must be valid utf-8 or an error will be returned
152#[no_mangle]
153pub unsafe extern "C" fn glu_push_string(vm: &Thread, s: &u8, len: usize) -> Error {
154    let s = match str::from_utf8(slice::from_raw_parts(s, len)) {
155        Ok(s) => s,
156        Err(_) => return Error::Unknown,
157    };
158    match s.vm_push(&mut vm.current_context()) {
159        Ok(()) => Error::Ok,
160        Err(_) => Error::Unknown,
161    }
162}
163
164/// Push a string to the stack. If the string is not utf-8 this function will trigger undefined
165/// behaviour.
166#[no_mangle]
167pub unsafe extern "C" fn glu_push_string_unchecked(vm: &Thread, s: &u8, len: usize) -> Error {
168    let s = str::from_utf8_unchecked(slice::from_raw_parts(s, len));
169    match s.vm_push(&mut vm.current_context()) {
170        Ok(()) => Error::Ok,
171        Err(_) => Error::Unknown,
172    }
173}
174
175#[cfg(not(target_arch = "wasm32"))]
176#[no_mangle]
177pub extern "C" fn glu_push_light_userdata(vm: &Thread, data: *mut libc::c_void) {
178    Thread::push(vm, data as usize).unwrap()
179}
180
181#[no_mangle]
182pub extern "C" fn glu_get_byte(vm: &Thread, index: VmIndex, out: &mut u8) -> Error {
183    get_value(vm, index, out)
184}
185
186#[no_mangle]
187pub extern "C" fn glu_get_int(vm: &Thread, index: VmIndex, out: &mut VmInt) -> Error {
188    get_value(vm, index, out)
189}
190
191#[no_mangle]
192pub extern "C" fn glu_get_float(vm: &Thread, index: VmIndex, out: &mut f64) -> Error {
193    get_value(vm, index, out)
194}
195
196#[no_mangle]
197pub extern "C" fn glu_get_bool(vm: &Thread, index: VmIndex, out: &mut i8) -> Error {
198    let mut b = false;
199    let err = get_value(vm, index, &mut b);
200    if err == Error::Ok {
201        *out = b as i8;
202    }
203    err
204}
205
206/// The returned string is garbage collected and may not be valid after the string is removed from
207/// its slot in the stack
208#[no_mangle]
209pub unsafe extern "C" fn glu_get_string(
210    vm: &Thread,
211    index: VmIndex,
212    out: &mut *const u8,
213    out_len: &mut usize,
214) -> Error {
215    let mut context = vm.context();
216    let stack = context.stack_frame::<stack::State>();
217    match stack
218        .get_variant(index)
219        .map(|value| <&str>::from_value(vm, value))
220    {
221        Some(value) => {
222            *out = &*value.as_ptr();
223            *out_len = value.len();
224            Error::Ok
225        }
226        None => Error::Unknown,
227    }
228}
229
230#[cfg(not(target_arch = "wasm32"))]
231#[no_mangle]
232pub extern "C" fn glu_get_light_userdata(
233    vm: &Thread,
234    index: VmIndex,
235    out: &mut *mut libc::c_void,
236) -> Error {
237    let mut userdata = 0usize;
238    let err = get_value(vm, index, &mut userdata);
239    if err == Error::Ok {
240        *out = userdata as *mut libc::c_void;
241    }
242    err
243}
244
245fn get_value<T>(vm: &Thread, index: VmIndex, out: &mut T) -> Error
246where
247    T: for<'vm, 'value> Getable<'vm, 'value>,
248{
249    let mut context = vm.context();
250    let stack = context.stack_frame::<stack::State>();
251    match stack
252        .get_variant(index)
253        .map(|value| T::from_value(vm, value))
254    {
255        Some(value) => {
256            *out = value;
257            Error::Ok
258        }
259        None => Error::Unknown,
260    }
261}
262
263#[cfg(test)]
264mod tests {
265    use super::*;
266
267    use gluon::vm::thread::{Status, Thread};
268
269    use std::{ptr, slice, str};
270
271    #[test]
272    fn push_pop() {
273        unsafe {
274            let vm = &*glu_new_vm();
275
276            glu_push_int(vm, 123);
277            glu_push_float(vm, 3.14);
278            let s = "test";
279            glu_push_string(vm, &s.as_bytes()[0], s.len());
280            glu_push_bool(vm, 1);
281            glu_push_byte(vm, 128);
282
283            let mut int = 0;
284            assert_eq!(glu_get_int(vm, 0, &mut int), Error::Ok);
285            assert_eq!(int, 123);
286
287            let mut float = 0.0;
288            assert_eq!(glu_get_float(vm, 1, &mut float), Error::Ok);
289            assert_eq!(float, 3.14);
290
291            let mut string_ptr = ptr::null();
292            let mut string_len = 0;
293            assert_eq!(
294                glu_get_string(vm, 2, &mut string_ptr, &mut string_len),
295                Error::Ok
296            );
297            assert_eq!(
298                str::from_utf8(slice::from_raw_parts(string_ptr, string_len)),
299                Ok("test")
300            );
301
302            let mut b = 0;
303            assert_eq!(glu_get_bool(vm, 3, &mut b), Error::Ok);
304            assert_eq!(b, 1);
305
306            let mut b = 0;
307            assert_eq!(glu_get_byte(vm, 4, &mut b), Error::Ok);
308            assert_eq!(b, 128);
309
310            assert_eq!(glu_len(vm), 5);
311
312            glu_pop(vm, 4);
313
314            glu_free_vm(vm);
315        }
316    }
317
318    #[cfg(not(target_arch = "wasm32"))]
319    #[test]
320    fn push_userdata() {
321        unsafe {
322            let vm = &*glu_new_vm();
323
324            let x = 123i32;
325            glu_push_light_userdata(vm, &x as *const i32 as *mut ::libc::c_void);
326            let mut p = ptr::null_mut();
327            assert_eq!(glu_get_light_userdata(vm, 0, &mut p), Error::Ok);
328            assert_eq!(*(p as *const i32), 123);
329        }
330    }
331
332    #[test]
333    fn call_function() {
334        extern "C" fn mult(vm: &Thread) -> Status {
335            let mut l = 0.0;
336            assert_eq!(glu_get_float(vm, 0, &mut l), Error::Ok);
337            let mut r = 0.0;
338            assert_eq!(glu_get_float(vm, 1, &mut r), Error::Ok);
339            glu_push_float(vm, l * r);
340            Status::Ok
341        }
342
343        unsafe {
344            let vm = &*glu_new_vm();
345            let name = "mult";
346            glu_push_function(vm, &name.as_bytes()[0], name.len(), mult, 2);
347            glu_push_float(vm, 12.0);
348            glu_push_float(vm, 3.0);
349
350            assert_eq!(glu_call_function(vm, 2), Error::Ok);
351            let mut result = 0.0;
352            assert_eq!(glu_get_float(vm, 0, &mut result), Error::Ok);
353            assert_eq!(result, 36.0);
354
355            glu_free_vm(vm);
356        }
357    }
358}