Skip to main content

intuicio_core/
lib.rs

1//! The building blocks every Intuicio scripting solution is made of.
2//!
3//! Intuicio is not a scripting language. It is a set of pieces to build one
4//! from. This crate holds the pieces that every part of such a build agrees
5//! on.
6//!
7//! # The pipeline
8//!
9//! ```text
10//! source (text, node graph, anything)
11//!   |  frontend
12//!   v
13//! script data  ->  backend  ->  Function in a Registry
14//!                                     ^
15//!                                     |  Host calls it in a Context
16//! ```
17//!
18//! - A **frontend** turns some input into [script data](script). It can be a
19//!   parser, a node graph editor, or anything else that produces the same
20//!   data.
21//! - A **backend** turns script data into a callable [`function::Function`]. A
22//!   virtual machine is the obvious one, a transpiler to Rust is another.
23//! - The **host** is the native side: Rust functions and types registered in a
24//!   [`registry::Registry`], callable from scripts and calling back into them.
25//!
26//! # Why script and native calls look the same
27//!
28//! Every function, native or scripted, has the same shape:
29//! `fn(&mut Context, &Registry)`. It pops its arguments off the context stack
30//! and pushes its results back. Neither side can tell which kind it is calling,
31//! so a program can mix frontends and backends freely.
32//!
33//! # Where to start
34//!
35//! - [`registry`] - where every type and function is declared.
36//! - [`context`] - the stack and registers a call runs on.
37//! - [`host`] - the convenient way to call into all of it.
38//! - [`script`] - the data a frontend produces and a backend consumes.
39//! - [`types`] - runtime descriptions of structs and enums.
40pub mod context;
41pub mod function;
42pub mod host;
43pub mod meta;
44pub mod object;
45pub mod registry;
46pub mod script;
47pub mod transformer;
48pub mod types;
49pub mod utils;
50
51/// Re-export used by the `define_native_struct!` macro to find field offsets.
52pub use memoffset::offset_of as __internal__offset_of__;
53
54/// Returns the byte offset of a field inside one variant of a `repr(u8)` enum.
55///
56/// Used by `define_native_enum!`. Only sound for `repr(u8)` enums, whose
57/// discriminant sits at offset zero.
58#[macro_export]
59macro_rules! __internal__offset_of_enum__ {
60    ($type:tt :: $variant:ident [ $( $field:ident ),* ] => $used_field:ident => $discriminant:literal) => {{
61        let mut data = std::mem::MaybeUninit::<$type>::uninit();
62        let ptr = data.as_mut_ptr().cast::<u8>();
63        #[allow(clippy::macro_metavars_in_unsafe)]
64        unsafe {
65            ptr.write($discriminant);
66            #[allow(unused)]
67            match data.assume_init_ref() {
68                $type::$variant( $( $field ),* ) => {
69                    ($used_field as *const _ as *const u8).offset_from(ptr) as usize
70                }
71                _ => unreachable!(),
72            }
73        }
74    }};
75    ($type:tt :: $variant:ident ( $index:tt ) => $discriminant:literal) => {{
76        let mut data = std::mem::MaybeUninit::<$type>::uninit();
77        let ptr = data.as_mut_ptr().cast::<u8>();
78        #[allow(clippy::macro_metavars_in_unsafe)]
79        unsafe {
80            ptr.write($discriminant);
81            #[allow(unused)]
82            match data.assume_init_ref() {
83                $type::$variant {
84                    $index: __value__, ..
85                } => (__value__ as *const _ as *const u8).offset_from(ptr) as usize,
86                _ => unreachable!(),
87            }
88        }
89    }};
90    ($type:tt :: $variant:ident { $field:ident } => $discriminant:literal) => {{
91        let mut data = std::mem::MaybeUninit::<$type>::uninit();
92        let ptr = data.as_mut_ptr().cast::<u8>();
93        #[allow(clippy::macro_metavars_in_unsafe)]
94        unsafe {
95            ptr.write($discriminant);
96            #[allow(unused)]
97            match data.assume_init_ref() {
98                $type::$variant { $field, .. } => {
99                    ($field as *const _ as *const u8).offset_from(ptr) as usize
100                }
101                _ => unreachable!(),
102            }
103        }
104    }};
105}
106
107use crate::{
108    registry::Registry,
109    types::{enum_type::Enum, struct_type::Struct},
110};
111use serde::{Deserialize, Serialize};
112
113/// A search filter for a field that the target may or may not have.
114///
115/// `Option` cannot say "must be absent". A query built from `Option` therefore
116/// cannot ask for a function with no type handle, which is how a free function
117/// is told apart from a method. A lookup by name would then also match every
118/// method of that name.
119///
120/// Use this where the target field is an `Option`, and plain `Option` where it
121/// is not.
122///
123/// ```
124/// # use intuicio_core::Filter;
125/// let ignore = Filter::<u32>::default();
126/// assert!(ignore.is_valid(None::<&u32>, |_, _| true));
127/// assert!(ignore.is_valid(Some(&1), |_, _| true));
128///
129/// assert!(Filter::<u32>::Absent.is_valid(None::<&u32>, |_, _| true));
130/// assert!(!Filter::<u32>::Absent.is_valid(Some(&1), |_, _| true));
131///
132/// assert!(Filter::Matching(1).is_valid(Some(&1), |query, value| query == value));
133/// assert!(!Filter::Matching(1).is_valid(None, |query, value| query == value));
134/// ```
135#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
136pub enum Filter<T> {
137    /// Matches whether the target has one or not.
138    #[default]
139    Ignore,
140    /// Matches only when the target has none.
141    Absent,
142    /// Matches only when the target has one that satisfies this.
143    Matching(T),
144}
145
146impl<T> Filter<T> {
147    /// Applies the filter to a target field, using `matches` to compare.
148    pub fn is_valid<U>(&self, value: Option<&U>, matches: impl FnOnce(&T, &U) -> bool) -> bool {
149        match self {
150            Self::Ignore => true,
151            Self::Absent => value.is_none(),
152            Self::Matching(query) => value.map(|value| matches(query, value)).unwrap_or(false),
153        }
154    }
155
156    /// Whether this filter says nothing, which is the default.
157    ///
158    /// Useful as a serde `skip_serializing_if`, so a query only writes the
159    /// filters it actually sets.
160    pub fn is_ignore(&self) -> bool {
161        matches!(self, Self::Ignore)
162    }
163
164    /// Whether this filter demands the target has none.
165    pub fn is_absent(&self) -> bool {
166        matches!(self, Self::Absent)
167    }
168
169    /// Returns what is being matched against, if anything.
170    pub fn matching(&self) -> Option<&T> {
171        match self {
172            Self::Matching(query) => Some(query),
173            _ => None,
174        }
175    }
176
177    /// Rebuilds the filter with a mapped payload, keeping the same setting.
178    ///
179    /// The named lifetime lets the result borrow from `self`, so an owned filter
180    /// can become a borrowing one.
181    pub fn map<'a, U>(&'a self, f: impl FnOnce(&'a T) -> U) -> Filter<U> {
182        match self {
183            Self::Ignore => Filter::Ignore,
184            Self::Absent => Filter::Absent,
185            Self::Matching(query) => Filter::Matching(f(query)),
186        }
187    }
188}
189
190impl<T> From<T> for Filter<T> {
191    fn from(value: T) -> Self {
192        Self::Matching(value)
193    }
194}
195
196/// `None` becomes [`Filter::Ignore`], not [`Filter::Absent`], because that is
197/// what `None` meant when these fields were `Option`.
198impl<T> From<Option<T>> for Filter<T> {
199    fn from(value: Option<T>) -> Self {
200        match value {
201            Some(value) => Self::Matching(value),
202            None => Self::Ignore,
203        }
204    }
205}
206
207/// How far a type, function or field can be seen from.
208///
209/// Ordered from narrowest to widest, so a wider visibility satisfies a
210/// narrower requirement. See [`Visibility::is_visible`].
211#[derive(
212    Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
213)]
214pub enum Visibility {
215    /// Visible only inside the type that declares it.
216    Private,
217    /// Visible inside the declaring module.
218    Module,
219    #[default]
220    /// Visible everywhere. The default.
221    Public,
222}
223
224impl Visibility {
225    /// Returns `true` when this visibility is at least as wide as `scope`.
226    ///
227    /// ```
228    /// # use intuicio_core::Visibility;
229    /// assert!(Visibility::Public.is_visible(Visibility::Module));
230    /// assert!(!Visibility::Private.is_visible(Visibility::Module));
231    /// ```
232    pub fn is_visible(self, scope: Self) -> bool {
233        self >= scope
234    }
235
236    /// Returns `true` for [`Visibility::Public`].
237    pub fn is_public(&self) -> bool {
238        *self == Visibility::Public
239    }
240
241    /// Returns `true` for [`Visibility::Module`].
242    pub fn is_module(&self) -> bool {
243        *self == Visibility::Module
244    }
245
246    /// Returns `true` for [`Visibility::Private`].
247    pub fn is_private(&self) -> bool {
248        *self == Visibility::Private
249    }
250}
251
252/// A Rust struct that can describe itself to a registry.
253///
254/// Implemented by the `IntuicioStruct` derive macro.
255pub trait IntuicioStruct {
256    /// Builds the runtime description of this struct.
257    ///
258    /// `registry` is needed to look up the types of the fields, so every field
259    /// type has to be registered first.
260    fn define_struct(registry: &Registry) -> Struct;
261}
262
263/// A Rust enum that can describe itself to a registry.
264///
265/// Implemented by the `IntuicioEnum` derive macro.
266pub trait IntuicioEnum {
267    /// Builds the runtime description of this enum.
268    ///
269    /// `registry` is needed to look up the types of the variant fields, so every
270    /// field type has to be registered first.
271    fn define_enum(registry: &Registry) -> Enum;
272}
273
274/// Semantic version of a crate, used to check that plugins match their host.
275///
276/// `repr(C)`, because it crosses the plugin ABI boundary.
277#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
278#[repr(C)]
279pub struct IntuicioVersion {
280    major: usize,
281    minor: usize,
282    patch: usize,
283}
284
285impl IntuicioVersion {
286    /// Builds a version.
287    pub fn new(major: usize, minor: usize, patch: usize) -> Self {
288        Self {
289            major,
290            minor,
291            patch,
292        }
293    }
294
295    /// Returns the major number.
296    pub fn major(&self) -> usize {
297        self.major
298    }
299
300    /// Returns the minor number.
301    pub fn minor(&self) -> usize {
302        self.minor
303    }
304
305    /// Returns the patch number.
306    pub fn patch(&self) -> usize {
307        self.patch
308    }
309
310    /// Returns `true` when major and minor match, ignoring the patch number.
311    pub fn is_compatible(&self, other: &Self) -> bool {
312        self.major == other.major && self.minor == other.minor
313    }
314}
315
316impl std::fmt::Display for IntuicioVersion {
317    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
318        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
319    }
320}
321
322impl std::fmt::Debug for IntuicioVersion {
323    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
324        f.debug_struct("IntuicioVersion")
325            .field("major", &self.major)
326            .field("minor", &self.minor)
327            .field("patch", &self.patch)
328            .finish()
329    }
330}
331
332/// Builds an [`IntuicioVersion`] from the `CARGO_PKG_VERSION_*` variables of
333/// the calling crate.
334#[macro_export]
335macro_rules! crate_version {
336    () => {{
337        let major = option_env!("CARGO_PKG_VERSION_MAJOR")
338            .unwrap_or("0")
339            .parse::<usize>()
340            .unwrap();
341        let minor = option_env!("CARGO_PKG_VERSION_MINOR")
342            .unwrap_or("0")
343            .parse::<usize>()
344            .unwrap();
345        let patch = option_env!("CARGO_PKG_VERSION_PATCH")
346            .unwrap_or("0")
347            .parse::<usize>()
348            .unwrap();
349        $crate::IntuicioVersion::new(major, minor, patch)
350    }};
351}
352
353/// Returns the version of this crate, for plugins to check against.
354pub fn core_version() -> IntuicioVersion {
355    crate_version!()
356}
357
358#[cfg(test)]
359mod tests {
360    use crate::Visibility;
361
362    #[test]
363    fn test_visibility() {
364        assert!(Visibility::Private.is_visible(Visibility::Private));
365        assert!(!Visibility::Private.is_visible(Visibility::Module));
366        assert!(!Visibility::Private.is_visible(Visibility::Public));
367        assert!(Visibility::Module.is_visible(Visibility::Private));
368        assert!(Visibility::Module.is_visible(Visibility::Module));
369        assert!(!Visibility::Module.is_visible(Visibility::Public));
370        assert!(Visibility::Public.is_visible(Visibility::Private));
371        assert!(Visibility::Public.is_visible(Visibility::Module));
372        assert!(Visibility::Public.is_visible(Visibility::Public));
373    }
374
375    #[test]
376    fn test_offset_of_enum() {
377        #[allow(dead_code)]
378        #[repr(u8)]
379        enum Foo {
380            A,
381            B(usize),
382            C(u8, u16),
383            D { a: u32, b: u64 },
384        }
385
386        assert_eq!(__internal__offset_of_enum__!(Foo::B[v] => v => 1), 8);
387        assert_eq!(__internal__offset_of_enum__!(Foo::B(0) => 1), 8);
388        assert_eq!(__internal__offset_of_enum__!(Foo::C[a, b] => a => 2), 1);
389        assert_eq!(__internal__offset_of_enum__!(Foo::C[a, b] => b => 2), 2);
390        assert_eq!(__internal__offset_of_enum__!(Foo::C(0) => 2), 1);
391        assert_eq!(__internal__offset_of_enum__!(Foo::C(1) => 2), 2);
392        assert_eq!(__internal__offset_of_enum__!(Foo::D { a } => 3), 4);
393        assert_eq!(__internal__offset_of_enum__!(Foo::D { b } => 3), 8);
394    }
395}