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}