Skip to main content

rhai/
engine.rs

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