Skip to main content

generic_lang_api/
export.rs

1//! The `export_module!` macro generating a plugin's `extern "C"` glue.
2
3/// Export the plugin's functions and classes to the generic interpreter.
4///
5/// Takes a comma-separated list of entries, each either:
6/// - A function: `(name, arities, function)`, where `arities` is a
7///   `&'static [u8]` of accepted argument counts and `function` is a
8///   [`RustPluginFn`](crate::RustPluginFn).
9/// - A class: `class("Name") { (method, arities, fun), ... }`, optionally
10///   followed by `drop: drop_fn,` and/or `traverse: traverse_fn,` (in that
11///   order). Method `arities` count `self`; the receiver arrives as `args[0]`.
12/// - A value: `value("name", creator)`, where `creator` is a
13///   [`RustPluginValueFn`](crate::RustPluginValueFn) building one module
14///   constant at import time.
15///
16/// ```ignore
17/// use generic_lang_api::{GenericValue, Host, PluginError, PluginVisitFn};
18///
19/// fn add(host: &mut Host, args: &[GenericValue]) -> Result<GenericValue, PluginError> {
20///     let (Some(a), Some(b)) = (host.as_int(args[0]), host.as_int(args[1])) else {
21///         return Err(host.type_error("add expects two integers"));
22///     };
23///     Ok(host.make_int(a + b))
24/// }
25///
26/// struct CounterState { value: i64 }
27/// // Methods take the receiver (`self`) as a separate parameter; `args` are the
28/// // remaining arguments, and arities exclude the receiver.
29/// fn counter_init(host: &mut Host, this: GenericValue, _args: &[GenericValue])
30///     -> Result<GenericValue, PluginError> {
31///     let ptr = Box::into_raw(Box::new(CounterState { value: 0 })).cast();
32///     host.set_opaque(this, ptr)?;
33///     Ok(this) // like every __init__, return the receiver
34/// }
35/// extern "C" fn drop_counter(ptr: *mut core::ffi::c_void) {
36///     if !ptr.is_null() { unsafe { drop(Box::from_raw(ptr.cast::<CounterState>())) }; }
37/// }
38///
39/// generic_lang_api::export_module![
40///     ("add", &[2], add),
41///     class("Counter") {
42///         ("__init__", &[0], counter_init), // no extra args beyond the receiver
43///         drop: drop_counter,
44///     },
45/// ];
46/// ```
47///
48/// Expands to static descriptor tables (the same shape a C plugin declares by
49/// hand) and the one symbol every plugin must export, `generic_plugin_init`,
50/// plus a panic-safe `extern "C"` wrapper per function/method (a panicking
51/// plugin call becomes a catchable generic exception instead of aborting the
52/// whole interpreter process, which is what an unwind reaching an `extern "C"`
53/// boundary does).
54#[macro_export]
55macro_rules! export_module {
56    [$($t:tt)*] => {
57        $crate::__export_go!(@go [] [] [] $($t)*);
58    };
59}
60
61/// Single tt-muncher behind [`export_module!`]: walks the entry list once,
62/// appending each entry to the functions, classes, or values accumulator,
63/// then emits `generic_plugin_init` with the `const` tables.
64#[doc(hidden)]
65#[macro_export]
66macro_rules! __export_go {
67    (@go [$($fa:tt)*] [$($ca:tt)*] [$($va:tt)*]) => {
68        /// Entry point resolved by the generic interpreter's plugin loader.
69        #[unsafe(no_mangle)]
70        pub extern "C" fn generic_plugin_init() -> *const $crate::ModuleDesc {
71            const FUNCTIONS: &[$crate::FunctionDesc] = &[ $($fa)* ];
72            const CLASSES: &[$crate::ClassDesc] = &[ $($ca)* ];
73            const VALUES: &[$crate::ValueDesc] = &[ $($va)* ];
74            static DESC: $crate::ModuleDesc = $crate::ModuleDesc {
75                abi_version: $crate::GENERIC_PLUGIN_ABI_VERSION,
76                functions: FUNCTIONS.as_ptr(),
77                functions_len: FUNCTIONS.len(),
78                classes: CLASSES.as_ptr(),
79                classes_len: CLASSES.len(),
80                values: VALUES.as_ptr(),
81                values_len: VALUES.len(),
82            };
83            &raw const DESC
84        }
85    };
86    (@go [$($fa:tt)*] [$($ca:tt)*] [$($va:tt)*] ($n:expr, $a:expr, $f:expr) $(, $($r:tt)*)?) => {
87        $crate::__export_go!(
88            @go [$($fa)* $crate::__function_desc!($n, $a, $f),] [$($ca)*] [$($va)*] $($($r)*)?
89        );
90    };
91    (@go [$($fa:tt)*] [$($ca:tt)*] [$($va:tt)*] class($cn:expr) { $($b:tt)* } $(, $($r:tt)*)?) => {
92        $crate::__export_go!(
93            @go [$($fa)*] [$($ca)* $crate::__class_desc!($cn, { $($b)* }),] [$($va)*] $($($r)*)?
94        );
95    };
96    (@go [$($fa:tt)*] [$($ca:tt)*] [$($va:tt)*] value($vn:expr, $vf:expr) $(, $($r:tt)*)?) => {
97        $crate::__export_go!(
98            @go [$($fa)*] [$($ca)*] [$($va)* $crate::__value_desc!($vn, $vf),] $($($r)*)?
99        );
100    };
101}
102
103/// `__opt!()` -> `None`; `__opt!(expr)` -> `Some(expr)` (const-friendly, for
104/// the optional `drop:` / `traverse:` fields).
105#[doc(hidden)]
106#[macro_export]
107macro_rules! __opt {
108    () => {
109        None
110    };
111    ($e:expr) => {
112        Some($e)
113    };
114}
115
116/// Build one [`FunctionDesc`](crate::FunctionDesc) with its panic-safe wrapper.
117#[doc(hidden)]
118#[macro_export]
119macro_rules! __function_desc {
120    ($name:expr, $arities:expr, $fun:expr) => {{
121        extern "C" fn wrapper(
122            host: *const $crate::HostApi,
123            args: *const $crate::GenericValue,
124            nargs: usize,
125        ) -> $crate::FfiReturn {
126            // SAFETY: the host passes a valid vtable and `nargs` contiguous
127            // argument values.
128            unsafe { $crate::__invoke_plugin_fn($fun, host, args, nargs) }
129        }
130        const NAME: &str = $name;
131        const ARITIES: &[u8] = $arities;
132        $crate::FunctionDesc {
133            name: $crate::FfiStr {
134                ptr: NAME.as_ptr(),
135                len: NAME.len(),
136            },
137            arities: ARITIES.as_ptr(),
138            arities_len: ARITIES.len(),
139            fun: Some(wrapper),
140        }
141    }};
142}
143
144/// Build one [`ValueDesc`](crate::ValueDesc) with its panic-safe wrapper.
145#[doc(hidden)]
146#[macro_export]
147macro_rules! __value_desc {
148    ($name:expr, $fun:expr) => {{
149        extern "C" fn wrapper(host: *const $crate::HostApi) -> $crate::FfiReturn {
150            // SAFETY: the host passes a valid vtable.
151            unsafe { $crate::__invoke_plugin_value_fn($fun, host) }
152        }
153        const NAME: &str = $name;
154        $crate::ValueDesc {
155            name: $crate::FfiStr {
156                ptr: NAME.as_ptr(),
157                len: NAME.len(),
158            },
159            fun: Some(wrapper),
160        }
161    }};
162}
163
164/// Build one [`MethodDesc`](crate::MethodDesc). The wrapper takes the receiver
165/// as a separate value and passes it through; `arities` exclude the receiver.
166#[doc(hidden)]
167#[macro_export]
168macro_rules! __method_desc {
169    ($name:expr, $arities:expr, $fun:expr) => {{
170        extern "C" fn wrapper(
171            host: *const $crate::HostApi,
172            receiver: $crate::GenericValue,
173            args: *const $crate::GenericValue,
174            nargs: usize,
175        ) -> $crate::FfiReturn {
176            // SAFETY: as in `__function_desc!`.
177            unsafe { $crate::__invoke_plugin_method_fn($fun, host, receiver, args, nargs) }
178        }
179        const NAME: &str = $name;
180        const ARITIES: &[u8] = $arities;
181        $crate::MethodDesc {
182            name: $crate::FfiStr {
183                ptr: NAME.as_ptr(),
184                len: NAME.len(),
185            },
186            arities: ARITIES.as_ptr(),
187            arities_len: ARITIES.len(),
188            fun: Some(wrapper),
189        }
190    }};
191}
192
193/// Build one [`ClassDesc`](crate::ClassDesc) from a `class(...) { ... }` body:
194/// method triples first, then optional `drop:` / `traverse:` (in that order).
195#[doc(hidden)]
196#[macro_export]
197macro_rules! __class_desc {
198    ($cname:expr, {
199        $( ($mn:expr, $ma:expr, $mf:expr) ),* $(,)?
200        $(drop: $drop:expr,)? $(traverse: $trav:expr,)?
201    }) => {{
202        const NAME: &str = $cname;
203        const METHODS: &[$crate::MethodDesc] =
204            &[ $( $crate::__method_desc!($mn, $ma, $mf) ),* ];
205        $crate::ClassDesc {
206            name: $crate::FfiStr { ptr: NAME.as_ptr(), len: NAME.len() },
207            methods: METHODS.as_ptr(),
208            methods_len: METHODS.len(),
209            drop: $crate::__opt!($($drop)?),
210            traverse: $crate::__opt!($($trav)?),
211        }
212    }};
213}