rhai 1.27.0

Embedded scripting for Rust
Documentation
//! Module defining macros for developing _plugins_.

use super::FnCallArgs;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;

/// Result of a Rhai function.
pub type RhaiResult = crate::RhaiResult;

pub use rhai_codegen::*;

// Removing these re-exports would be a breaking change, despite there are alternate import paths.
// https://github.com/rhaiscript/rhai/issues/1098
pub use super::RhaiFunc;
pub use crate::{
    Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, FuncRegistration, ImmutableString,
    Module, NativeCallContext, Position,
};
pub use std::{any::TypeId, mem};

/// Trait implemented by a _plugin function_.
///
/// This trait should not be used directly.
pub trait PluginFunc {
    /// Call the plugin function with the arguments provided.
    fn call(&self, context: Option<NativeCallContext>, args: &mut FnCallArgs) -> RhaiResult;

    /// Is this plugin function a method?
    #[must_use]
    fn is_method_call(&self) -> bool;

    /// Does this plugin function contain a [`NativeCallContext`] parameter?
    #[must_use]
    fn has_context(&self) -> bool;

    /// Is this plugin function pure?
    ///
    /// Defaults to `true` such that any old implementation that has constant-checking code inside
    /// the function itself will continue to work.
    #[inline(always)]
    #[must_use]
    fn is_pure(&self) -> bool {
        true
    }

    /// Is this plugin function volatile?
    ///
    /// A volatile function is not guaranteed to return the same result for the same input(s).
    ///
    /// Defaults to `true` such that any old implementation that has constant-checking code inside
    /// the function itself will continue to work.
    #[inline(always)]
    #[must_use]
    fn is_volatile(&self) -> bool {
        true
    }
}

/// Static manifest of a _plugin module_, generated by `#[export_module(manifest)]`.
///
/// It lists every exported function without registering any of them. A [`Module`] holding a
/// manifest (see [`Module::from_manifest`] and [`Module::combine_manifest`]) looks up its functions
/// by hash only when they are called.
///
/// Fields are public only so that generated code can build a manifest in a `static`. Use the
/// methods instead.
#[derive(Clone, Copy)]
pub struct ModuleManifest {
    /// Exported functions.
    #[cfg_attr(not(feature = "internals"), doc(hidden))]
    pub functions: &'static [FnManifestEntry],
    /// Sub-modules.
    #[cfg_attr(not(feature = "internals"), doc(hidden))]
    pub sub_modules: &'static [(&'static str, &'static ModuleManifest)],
    /// Register everything that is not a function (i.e. constants and custom types) into a [`Module`].
    #[cfg_attr(not(feature = "internals"), doc(hidden))]
    pub init_eager: fn(&mut Module),
}

/// A function entry in a [`ModuleManifest`].
///
/// Fields are public only so that generated code can build a manifest in a `static`. Use the
/// methods instead.
#[derive(Clone, Copy)]
pub struct FnManifestEntry {
    /// Name of the function, as registered.
    #[cfg_attr(not(feature = "internals"), doc(hidden))]
    pub name: &'static str,
    /// Function namespace.
    #[cfg_attr(not(feature = "internals"), doc(hidden))]
    pub namespace: FnNamespace,
    /// The plugin function.
    ///
    /// It is always [`Send`] + [`Sync`] because it lives in a `static`.
    #[cfg_attr(not(feature = "internals"), doc(hidden))]
    pub func: &'static (dyn PluginFunc + Send + Sync),
    /// Call a function with the parameter types of the plugin function.
    #[cfg_attr(not(feature = "internals"), doc(hidden))]
    pub param_types: fn(&mut dyn FnMut(&[TypeId])),
    /// Register this function (and only this function), with full metadata, into a [`Module`].
    #[cfg_attr(not(feature = "internals"), doc(hidden))]
    pub register: fn(&mut Module),
}

// `Debug` cannot be derived for higher-ranked function pointers under the MSRV.
impl std::fmt::Debug for ModuleManifest {
    #[cold]
    #[inline(never)]
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ModuleManifest")
            .field("functions", &self.functions)
            .field("sub_modules", &self.sub_modules)
            .finish_non_exhaustive()
    }
}

impl std::fmt::Debug for FnManifestEntry {
    #[cold]
    #[inline(never)]
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("FnManifestEntry")
            .field("name", &self.name)
            .field("namespace", &self.namespace)
            .finish_non_exhaustive()
    }
}

impl ModuleManifest {
    /// Get the exported functions, not including those in sub-modules.
    #[inline(always)]
    #[must_use]
    pub const fn functions(&self) -> &'static [FnManifestEntry] {
        self.functions
    }
    /// Get the sub-modules, with their names.
    #[inline(always)]
    #[must_use]
    pub const fn sub_modules(&self) -> &'static [(&'static str, &'static ModuleManifest)] {
        self.sub_modules
    }
    /// Register everything that is not a function (i.e. constants and custom types) into a
    /// [`Module`], not including sub-modules.
    #[inline(always)]
    pub fn init_eager(&self, module: &mut Module) {
        (self.init_eager)(module);
    }
    /// Call a function on every exported function, optionally including those in sub-modules
    /// (flattened), from the lowest precedence to the highest.
    ///
    /// Registering each function in turn leaves the one with the highest precedence in place.
    pub fn for_each_fn(&'static self, flatten: bool, f: &mut dyn FnMut(&'static FnManifestEntry)) {
        self.functions.iter().for_each(&mut *f);

        if flatten {
            for (.., m) in self.sub_modules {
                m.for_each_fn(flatten, f);
            }
        }
    }
    /// Find the exported function with the highest precedence that matches a predicate,
    /// optionally including those in sub-modules (flattened, taking precedence).
    #[must_use]
    pub fn find_fn(
        &'static self,
        flatten: bool,
        f: &mut dyn FnMut(&FnManifestEntry) -> bool,
    ) -> Option<&'static FnManifestEntry> {
        let found = flatten
            .then(|| {
                let mut sub_modules = self.sub_modules.iter().rev();
                sub_modules.find_map(|(.., m)| m.find_fn(flatten, f))
            })
            .flatten();

        found.or_else(|| self.functions.iter().rev().find(|&e| f(e)))
    }
}

impl FnManifestEntry {
    /// Get the name of the function, as registered.
    #[inline(always)]
    #[must_use]
    pub const fn name(&self) -> &'static str {
        self.name
    }
    /// Get the function namespace.
    #[inline(always)]
    #[must_use]
    pub const fn namespace(&self) -> FnNamespace {
        self.namespace
    }
    /// Get the plugin function.
    #[inline(always)]
    #[must_use]
    pub const fn func(&self) -> &'static (dyn PluginFunc + Send + Sync) {
        self.func
    }
    /// Call a function with the parameter types of the plugin function.
    #[inline(always)]
    pub fn with_param_types(&self, f: &mut dyn FnMut(&[TypeId])) {
        (self.param_types)(f);
    }
    /// Register this function (and only this function), with full metadata, into a [`Module`].
    #[inline(always)]
    pub fn register_into(&self, module: &mut Module) {
        (self.register)(module);
    }
    /// Get the number of parameters.
    #[must_use]
    pub fn num_params(&self) -> usize {
        let mut num_params = 0;
        self.with_param_types(&mut |types| num_params = types.len());
        num_params
    }
    /// Calculate the hash of this function under a namespace path, the same as when it is
    /// registered into (or indexed by) a [`Module`].
    #[must_use]
    pub fn calc_hash(&self, path: &[&str]) -> u64 {
        let is_method = self.func.is_method_call();
        let mut hash = 0;

        self.with_param_types(&mut |types| {
            let hash_script = crate::calc_fn_hash(path.iter().copied(), self.name, types.len());
            let types = types.iter().enumerate();
            hash = crate::calc_fn_hash_full(
                hash_script,
                types.map(|(i, &t)| Module::map_type(!is_method || i > 0, t)),
            );
        });

        hash
    }
    /// If this function has any [`Dynamic`] parameter, get its hash in scripted format (i.e.
    /// without parameter types).
    #[must_use]
    pub fn calc_dynamic_hash(&self) -> Option<u64> {
        let mut hash = None;

        self.with_param_types(&mut |types| {
            if types.contains(&TypeId::of::<Dynamic>()) {
                hash = Some(crate::calc_fn_hash(None, self.name, types.len()));
            }
        });

        hash
    }
}