generic_lang_api/lib.rs
1//! # Plugin ABI and authoring API for the generic programming language
2//!
3//! Native modules for generic are shared libraries speaking the C ABI
4//! defined in [`abi`]. Rust plugin authors use the safe layer in this crate
5//! root; other languages use the generated `include/generic.h`.
6//!
7//! This crate is versioned independently from the interpreter - it tracks ABI
8//! stability for plugin authors. Build against the version matching the
9//! target interpreter ([`GENERIC_PLUGIN_ABI_VERSION`] is checked at load).
10//!
11//! See the [plugin authoring guide][guide] for worked quickstarts (Rust and
12//! C), the value model, calling back into generic, the exception model, and
13//! the rooting contract.
14//!
15//! [guide]: https://github.com/JanEricNitschke/generic-lang/blob/main/docs/plugin-authors.md
16//!
17//! # Example
18//!
19//! ```no_run
20//! use generic_lang_api::{ArgValue, GenericValue, Host, PluginError};
21//!
22//! fn add(host: &mut Host, args: &[GenericValue]) -> Result<GenericValue, PluginError> {
23//! match (host.decode(args[0]), host.decode(args[1])) {
24//! (ArgValue::Int(a), ArgValue::Int(b)) => Ok(host.make_int(a + b)),
25//! _ => Err(host.type_error("add expects two ints")),
26//! }
27//! }
28//!
29//! generic_lang_api::export_module![("add", &[2], add)];
30//! ```
31#![warn(missing_docs)]
32
33pub mod abi;
34mod export;
35mod host;
36
37pub use abi::{
38 ClassDesc, FfiReturn, FfiStatus, FfiStr, FunctionDesc, GENERIC_PLUGIN_ABI_VERSION,
39 GenericValue, HostApi, MethodDesc, ModuleDesc, PluginFn, PluginMethodFn, PluginTraverseFn,
40 PluginValueFn, PluginVisitFn, ValueDesc,
41};
42pub use host::{
43 __invoke_plugin_fn, __invoke_plugin_method_fn, __invoke_plugin_value_fn, ArgValue, Host,
44 Rooted, RustPluginFn, RustPluginMethodFn, RustPluginValueFn,
45};
46
47/// Value kinds returned by [`HostApi::value_kind`].
48///
49/// Over the FFI these travel as plain `u32` - convert with `as u32` /
50/// [`ValueKind::from_u32`]. The host-side mapping (and the coverage test
51/// guarding that every interpreter value maps to one of these) lives in
52/// the feature-gated plugin module in the interpreter crate.
53#[repr(u32)]
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub enum ValueKind {
56 /// The `nil` value.
57 Nil = 0,
58 /// A boolean.
59 Bool = 1,
60 /// An integer that fits in an `i64` (`int_get` succeeds).
61 Int = 2,
62 /// An integer that does not fit in an `i64` (`int_get` fails; use
63 /// `value_display`/`value_str`).
64 BigInt = 3,
65 /// A float.
66 Float = 4,
67 /// A rational number.
68 Rational = 5,
69 /// A string (`string_get` succeeds).
70 String = 6,
71 /// A list (`list_len`/`list_get` succeed).
72 List = 7,
73 /// A tuple.
74 Tuple = 8,
75 /// A dict.
76 Dict = 9,
77 /// A set.
78 Set = 10,
79 /// A range.
80 Range = 11,
81 /// The `StopIteration` sentinel - what `__next__` returns when an
82 /// iterator is exhausted.
83 StopIteration = 12,
84 /// A plain class instance (fields via `attr_get`/`attr_set`, methods
85 /// via `invoke_method`).
86 Instance = 13,
87 /// A class (instantiate via `call_value`).
88 Class = 14,
89 /// A callable function value (closure, native function, or bound
90 /// method) - use `call_value`.
91 Function = 15,
92 /// A module.
93 Module = 16,
94 /// An exception instance.
95 Exception = 17,
96 /// A generator.
97 Generator = 18,
98 /// A list/tuple/range/template iterator (drive via `invoke_method`
99 /// with `__next__`).
100 Iterator = 19,
101 /// VM-internal values a plugin should never meaningfully receive.
102 Other = 20,
103}
104
105impl ValueKind {
106 /// Decode a raw kind code from [`HostApi::value_kind`]; unknown codes
107 /// map to [`ValueKind::Other`].
108 #[must_use]
109 pub const fn from_u32(code: u32) -> Self {
110 match code {
111 0 => Self::Nil,
112 1 => Self::Bool,
113 2 => Self::Int,
114 3 => Self::BigInt,
115 4 => Self::Float,
116 5 => Self::Rational,
117 6 => Self::String,
118 7 => Self::List,
119 8 => Self::Tuple,
120 9 => Self::Dict,
121 10 => Self::Set,
122 11 => Self::Range,
123 12 => Self::StopIteration,
124 13 => Self::Instance,
125 14 => Self::Class,
126 15 => Self::Function,
127 16 => Self::Module,
128 17 => Self::Exception,
129 18 => Self::Generator,
130 19 => Self::Iterator,
131 _ => Self::Other,
132 }
133 }
134}
135
136/// The error side of a plugin function: what should reach the generic VM
137/// when the function does not return normally.
138///
139/// An exact mirror of the two non-ok wire statuses
140/// ([`FfiStatus::Exception`] / [`FfiStatus::Fatal`]).
141///
142/// Failed re-entering host calls hand back
143/// [`PluginError::Exception`] (the raised exception *instance*) or
144/// [`PluginError::Fatal`]; propagating them with `?` re-raises the
145/// exception with full identity - class, fields, and original stack trace
146/// or forwards the fatal error, respectively. To throw a fresh
147/// exception, use the typed constructors on [`Host`]
148/// (`host.type_error("…")`, …), which create the instance eagerly; for
149/// non-builtin classes, build the instance with
150/// [`Host::make_exception`] (or by calling the class) and wrap it:
151///
152/// ```ignore
153/// fn half(host: &mut Host, args: &[GenericValue]) -> Result<GenericValue, PluginError> {
154/// let Some(n) = host.as_int(args[0]) else {
155/// return Err(host.type_error("half expects an int"));
156/// };
157/// Ok(host.make_int(n / 2))
158/// }
159/// ```
160#[derive(Debug, Clone, Copy)]
161pub enum PluginError {
162 /// An exception instance - freshly created, or caught from a
163 /// re-entering host call - to be (re-)raised.
164 Exception(GenericValue),
165 /// A fatal host error passing through. Only ever forwarded; plugins
166 /// must not fabricate it.
167 Fatal,
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 #[test]
175 fn abi_type_layout() {
176 assert_eq!(size_of::<GenericValue>(), 32);
177 assert_eq!(align_of::<GenericValue>(), 8);
178 assert_eq!(size_of::<FfiReturn>(), 40);
179 assert_eq!(size_of::<FfiStr>(), 2 * size_of::<usize>());
180 }
181
182 /// The crate version encodes the ABI version: `0.<abi>.<patch>`. A
183 /// major bump would decouple the two schemes, and an ABI bump without
184 /// the matching minor bump would publish a crate whose version lies
185 /// about compatibility.
186 #[test]
187 fn crate_version_encodes_the_abi_version() {
188 let mut parts = env!("CARGO_PKG_VERSION").split('.');
189 let major: u32 = parts.next().unwrap().parse().unwrap();
190 let minor: u32 = parts.next().unwrap().parse().unwrap();
191 assert_eq!(major, 0, "the version scheme is 0.<abi>.<patch>");
192 assert_eq!(
193 minor, GENERIC_PLUGIN_ABI_VERSION,
194 "bump GENERIC_PLUGIN_ABI_VERSION and the crate minor version together"
195 );
196 }
197
198 #[test]
199 fn kind_round_trips() {
200 for kind in [
201 ValueKind::Nil,
202 ValueKind::Bool,
203 ValueKind::Int,
204 ValueKind::BigInt,
205 ValueKind::Float,
206 ValueKind::Rational,
207 ValueKind::String,
208 ValueKind::List,
209 ValueKind::Tuple,
210 ValueKind::Dict,
211 ValueKind::Set,
212 ValueKind::Range,
213 ValueKind::StopIteration,
214 ValueKind::Instance,
215 ValueKind::Class,
216 ValueKind::Function,
217 ValueKind::Module,
218 ValueKind::Exception,
219 ValueKind::Generator,
220 ValueKind::Iterator,
221 ValueKind::Other,
222 ] {
223 assert_eq!(ValueKind::from_u32(kind as u32), kind);
224 }
225 assert_eq!(ValueKind::from_u32(999), ValueKind::Other);
226 }
227}