spo_rhai/
engine.rs

1//! Main module defining the script evaluation [`Engine`].
2
3use crate::api::options::LangOptions;
4use crate::func::native::{
5    locked_write, OnDebugCallback, OnDefVarCallback, OnParseTokenCallback, OnPrintCallback,
6    OnVarCallback,
7};
8use crate::packages::{Package, StandardPackage};
9use crate::tokenizer::Token;
10use crate::types::StringsInterner;
11use crate::{Dynamic, Identifier, ImmutableString, Locked, OptimizationLevel, SharedModule};
12#[cfg(feature = "no_std")]
13use std::prelude::v1::*;
14use std::{collections::BTreeSet, fmt, num::NonZeroU8};
15
16pub type Precedence = NonZeroU8;
17
18pub const KEYWORD_PRINT: &str = "print";
19pub const KEYWORD_DEBUG: &str = "debug";
20pub const KEYWORD_TYPE_OF: &str = "type_of";
21pub const KEYWORD_EVAL: &str = "eval";
22pub const KEYWORD_FN_PTR: &str = "Fn";
23pub const KEYWORD_FN_PTR_CALL: &str = "call";
24pub const KEYWORD_FN_PTR_CURRY: &str = "curry";
25#[cfg(not(feature = "no_closure"))]
26pub const KEYWORD_IS_SHARED: &str = "is_shared";
27pub const KEYWORD_IS_DEF_VAR: &str = "is_def_var";
28#[cfg(not(feature = "no_function"))]
29pub const KEYWORD_IS_DEF_FN: &str = "is_def_fn";
30#[cfg(not(feature = "no_function"))]
31pub const KEYWORD_THIS: &str = "this";
32#[cfg(not(feature = "no_function"))]
33#[cfg(not(feature = "no_module"))]
34pub const KEYWORD_GLOBAL: &str = "global";
35#[cfg(not(feature = "no_object"))]
36pub const FN_GET: &str = "get$";
37#[cfg(not(feature = "no_object"))]
38pub const FN_SET: &str = "set$";
39#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
40pub const FN_IDX_GET: &str = "index$get$";
41#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
42pub const FN_IDX_SET: &str = "index$set$";
43#[cfg(not(feature = "no_function"))]
44pub const FN_ANONYMOUS: &str = "anon$";
45
46/// Standard equality comparison operator.
47///
48/// Some standard functions (e.g. searching an [`Array`][crate::Array]) implicitly call this
49/// function to compare two [`Dynamic`] values.
50pub const OP_EQUALS: &str = Token::EqualsTo.literal_syntax();
51
52/// Standard containment testing function.
53///
54/// The `in` operator is implemented as a call to this function.
55pub const OP_CONTAINS: &str = "contains";
56
57/// Standard not operator.
58pub const OP_NOT: &str = Token::Bang.literal_syntax();
59
60/// Separator for namespaces.
61#[cfg(not(feature = "no_module"))]
62pub const NAMESPACE_SEPARATOR: &str = Token::DoubleColon.literal_syntax();
63
64/// Rhai main scripting engine.
65///
66/// # Thread Safety
67///
68/// [`Engine`] is re-entrant.
69///
70/// Currently, [`Engine`] is neither [`Send`] nor [`Sync`].
71/// Use the `sync` feature to make it [`Send`] `+` [`Sync`].
72///
73/// # Example
74///
75/// ```
76/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
77/// use rhai::Engine;
78///
79/// let engine = Engine::new();
80///
81/// let result = engine.eval::<i64>("40 + 2")?;
82///
83/// println!("Answer: {result}");   // prints 42
84/// # Ok(())
85/// # }
86/// ```
87pub struct Engine {
88    /// A collection of all modules loaded into the global namespace of the Engine.
89    pub(crate) global_modules: Vec<SharedModule>,
90    /// A collection of all sub-modules directly loaded into the Engine.
91    #[cfg(not(feature = "no_module"))]
92    pub(crate) global_sub_modules: std::collections::BTreeMap<Identifier, SharedModule>,
93
94    /// A module resolution service.
95    #[cfg(not(feature = "no_module"))]
96    pub(crate) module_resolver: Option<Box<dyn crate::ModuleResolver>>,
97
98    /// Strings interner.
99    pub(crate) interned_strings: Option<Box<Locked<StringsInterner>>>,
100
101    /// A set of symbols to disable.
102    pub(crate) disabled_symbols: BTreeSet<Identifier>,
103    /// A map containing custom keywords and precedence to recognize.
104    #[cfg(not(feature = "no_custom_syntax"))]
105    pub(crate) custom_keywords: std::collections::BTreeMap<Identifier, Option<Precedence>>,
106    /// Custom syntax.
107    #[cfg(not(feature = "no_custom_syntax"))]
108    pub(crate) custom_syntax:
109        std::collections::BTreeMap<Identifier, Box<crate::api::custom_syntax::CustomSyntax>>,
110
111    /// Callback closure for filtering variable definition.
112    pub(crate) def_var_filter: Option<Box<OnDefVarCallback>>,
113    /// Callback closure for resolving variable access.
114    pub(crate) resolve_var: Option<Box<OnVarCallback>>,
115    /// Callback closure to remap tokens during parsing.
116    pub(crate) token_mapper: Option<Box<OnParseTokenCallback>>,
117
118    /// Callback closure for implementing the `print` command.
119    pub(crate) print: Option<Box<OnPrintCallback>>,
120    /// Callback closure for implementing the `debug` command.
121    pub(crate) debug: Option<Box<OnDebugCallback>>,
122    /// Callback closure for progress reporting.
123    #[cfg(not(feature = "unchecked"))]
124    pub(crate) progress: Option<Box<crate::func::native::OnProgressCallback>>,
125
126    /// Language options.
127    pub(crate) options: LangOptions,
128
129    /// Default value for the custom state.
130    pub(crate) def_tag: Dynamic,
131
132    /// Script optimization level.
133    pub(crate) optimization_level: OptimizationLevel,
134
135    /// Max limits.
136    #[cfg(not(feature = "unchecked"))]
137    pub(crate) limits: crate::api::limits::Limits,
138
139    /// Callback closure for debugging.
140    #[cfg(feature = "debugging")]
141    pub(crate) debugger_interface: Option<(
142        Box<crate::eval::OnDebuggingInit>,
143        Box<crate::eval::OnDebuggerCallback>,
144    )>,
145}
146
147impl fmt::Debug for Engine {
148    #[cold]
149    #[inline(never)]
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        let mut f = f.debug_struct("Engine");
152
153        f.field("global_modules", &self.global_modules);
154
155        #[cfg(not(feature = "no_module"))]
156        f.field("global_sub_modules", &self.global_sub_modules);
157
158        f.field("disabled_symbols", &self.disabled_symbols);
159
160        #[cfg(not(feature = "no_custom_syntax"))]
161        f.field("custom_keywords", &self.custom_keywords).field(
162            "custom_syntax",
163            &self
164                .custom_syntax
165                .keys()
166                .map(crate::SmartString::as_str)
167                .collect::<String>(),
168        );
169
170        f.field("def_var_filter", &self.def_var_filter.is_some())
171            .field("resolve_var", &self.resolve_var.is_some())
172            .field("token_mapper", &self.token_mapper.is_some());
173
174        #[cfg(not(feature = "unchecked"))]
175        f.field("progress", &self.progress.is_some());
176
177        f.field("options", &self.options)
178            .field("default_tag", &self.def_tag);
179
180        #[cfg(not(feature = "no_optimize"))]
181        f.field("optimization_level", &self.optimization_level);
182
183        #[cfg(not(feature = "unchecked"))]
184        f.field("limits", &self.limits);
185
186        #[cfg(feature = "debugging")]
187        f.field("debugger_interface", &self.debugger_interface.is_some());
188
189        f.finish()
190    }
191}
192
193impl Default for Engine {
194    #[inline(always)]
195    #[must_use]
196    fn default() -> Self {
197        Self::new()
198    }
199}
200
201/// Make getter function
202#[cfg(not(feature = "no_object"))]
203#[inline(always)]
204#[must_use]
205pub fn make_getter(id: &str) -> Identifier {
206    let mut buf = Identifier::new_const();
207    buf.push_str(FN_GET);
208    buf.push_str(id);
209    buf
210}
211
212/// Make setter function
213#[cfg(not(feature = "no_object"))]
214#[inline(always)]
215#[must_use]
216pub fn make_setter(id: &str) -> Identifier {
217    let mut buf = Identifier::new_const();
218    buf.push_str(FN_SET);
219    buf.push_str(id);
220    buf
221}
222
223impl Engine {
224    /// An empty raw [`Engine`].
225    pub const RAW: Self = Self {
226        global_modules: Vec::new(),
227
228        #[cfg(not(feature = "no_module"))]
229        global_sub_modules: std::collections::BTreeMap::new(),
230
231        #[cfg(not(feature = "no_module"))]
232        module_resolver: None,
233
234        interned_strings: None,
235        disabled_symbols: BTreeSet::new(),
236        #[cfg(not(feature = "no_custom_syntax"))]
237        custom_keywords: std::collections::BTreeMap::new(),
238        #[cfg(not(feature = "no_custom_syntax"))]
239        custom_syntax: std::collections::BTreeMap::new(),
240
241        def_var_filter: None,
242        resolve_var: None,
243        token_mapper: None,
244
245        print: None,
246        debug: None,
247
248        #[cfg(not(feature = "unchecked"))]
249        progress: None,
250
251        options: LangOptions::new(),
252
253        def_tag: Dynamic::UNIT,
254
255        #[cfg(not(feature = "no_optimize"))]
256        optimization_level: OptimizationLevel::Simple,
257        #[cfg(feature = "no_optimize")]
258        optimization_level: (),
259
260        #[cfg(not(feature = "unchecked"))]
261        limits: crate::api::limits::Limits::new(),
262
263        #[cfg(feature = "debugging")]
264        debugger_interface: None,
265    };
266
267    /// Create a new [`Engine`].
268    #[inline]
269    #[must_use]
270    pub fn new() -> Self {
271        // Create the new scripting Engine
272        let mut engine = Self::new_raw();
273
274        #[cfg(not(feature = "no_module"))]
275        #[cfg(not(feature = "no_std"))]
276        #[cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))]
277        {
278            engine.module_resolver =
279                Some(Box::new(crate::module::resolvers::FileModuleResolver::new()));
280        }
281
282        engine.interned_strings = Some(Locked::new(StringsInterner::new()).into());
283
284        // default print/debug implementations
285        #[cfg(not(feature = "no_std"))]
286        #[cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))]
287        {
288            engine.print = Some(Box::new(|s| println!("{s}")));
289            engine.debug = Some(Box::new(|s, source, pos| match (source, pos) {
290                (Some(source), crate::Position::NONE) => println!("{source} | {s}"),
291                #[cfg(not(feature = "no_position"))]
292                (Some(source), pos) => println!("{source} @ {pos:?} | {s}"),
293                (None, crate::Position::NONE) => println!("{s}"),
294                #[cfg(not(feature = "no_position"))]
295                (None, pos) => println!("{pos:?} | {s}"),
296            }));
297        }
298
299        engine.register_global_module(StandardPackage::new().as_shared_module());
300
301        engine
302    }
303
304    /// Create a new [`Engine`] with minimal built-in functions.
305    /// It returns a copy of [`Engine::RAW`].
306    ///
307    /// This is useful for creating a custom scripting engine with only the functions you need.
308    ///
309    /// Use [`register_global_module`][Engine::register_global_module] to add packages of functions.
310    #[inline]
311    #[must_use]
312    pub const fn new_raw() -> Self {
313        Self::RAW
314    }
315
316    /// Get an interned [string][ImmutableString].
317    ///
318    /// [`Engine`] keeps a cache of [`ImmutableString`] instances and tries to avoid new allocations
319    /// and save memory when an existing instance is found.
320    ///
321    /// It is usually a good idea to intern strings if they are used frequently.
322    #[inline]
323    #[must_use]
324    pub fn get_interned_string(
325        &self,
326        string: impl AsRef<str> + Into<ImmutableString>,
327    ) -> ImmutableString {
328        match self.interned_strings {
329            Some(ref interner) => locked_write(interner).get(string),
330            None => string.into(),
331        }
332    }
333
334    /// Get an empty [`ImmutableString`] which refers to a shared instance.
335    #[inline(always)]
336    #[must_use]
337    pub fn const_empty_string(&self) -> ImmutableString {
338        self.get_interned_string("")
339    }
340
341    /// Is there a debugger interface registered with this [`Engine`]?
342    #[cfg(feature = "debugging")]
343    #[inline(always)]
344    #[must_use]
345    pub(crate) const fn is_debugger_registered(&self) -> bool {
346        self.debugger_interface.is_some()
347    }
348}