Skip to main content

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    }
178
179    /// The crate version encodes the ABI version: `0.<abi>.<patch>`. A
180    /// major bump would decouple the two schemes, and an ABI bump without
181    /// the matching minor bump would publish a crate whose version lies
182    /// about compatibility.
183    #[test]
184    fn crate_version_encodes_the_abi_version() {
185        let mut parts = env!("CARGO_PKG_VERSION").split('.');
186        let major: u32 = parts.next().unwrap().parse().unwrap();
187        let minor: u32 = parts.next().unwrap().parse().unwrap();
188        assert_eq!(major, 0, "the version scheme is 0.<abi>.<patch>");
189        assert_eq!(
190            minor, GENERIC_PLUGIN_ABI_VERSION,
191            "bump GENERIC_PLUGIN_ABI_VERSION and the crate minor version together"
192        );
193    }
194
195    #[test]
196    fn kind_round_trips() {
197        for kind in [
198            ValueKind::Nil,
199            ValueKind::Bool,
200            ValueKind::Int,
201            ValueKind::BigInt,
202            ValueKind::Float,
203            ValueKind::Rational,
204            ValueKind::String,
205            ValueKind::List,
206            ValueKind::Tuple,
207            ValueKind::Dict,
208            ValueKind::Set,
209            ValueKind::Range,
210            ValueKind::StopIteration,
211            ValueKind::Instance,
212            ValueKind::Class,
213            ValueKind::Function,
214            ValueKind::Module,
215            ValueKind::Exception,
216            ValueKind::Generator,
217            ValueKind::Iterator,
218            ValueKind::Other,
219        ] {
220            assert_eq!(ValueKind::from_u32(kind as u32), kind);
221        }
222        assert_eq!(ValueKind::from_u32(999), ValueKind::Other);
223    }
224}