Skip to main content

rhai/api/
eval.rs

1//! Module that defines the public evaluation API of [`Engine`].
2
3use crate::tokenizer::TokenStream;
4use crate::ast::{Expr, FnCallHashes};
5use crate::eval::{Caches, GlobalRuntimeState};
6use crate::parser::ParseState;
7use crate::tokenizer::Token;
8use crate::types::dynamic::Variant;
9use crate::{
10    calc_fn_hash, Dynamic, Engine, FnArgsVec, FuncArgs, Position, RhaiResult, RhaiResultOf, Scope,
11    AST, ERR,
12};
13#[cfg(feature = "no_std")]
14use std::prelude::v1::*;
15use std::{
16    any::{type_name, TypeId},
17    mem,
18};
19
20impl Engine {
21    /// Evaluate a string as a script, returning the result value or an error.
22    ///
23    /// # Example
24    ///
25    /// ```
26    /// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
27    /// use rhai::Engine;
28    ///
29    /// let engine = Engine::new();
30    ///
31    /// assert_eq!(engine.eval::<i64>("40 + 2")?, 42);
32    /// # Ok(())
33    /// # }
34    /// ```
35    #[inline(always)]
36    pub fn eval<T: Variant + Clone>(&self, script: &str) -> RhaiResultOf<T> {
37        self.eval_with_scope(&mut Scope::new(), script)
38    }
39    /// Evaluate a string as a script with own scope, returning the result value or an error.
40    ///
41    /// ## Constants Propagation
42    ///
43    /// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within
44    /// the scope are propagated throughout the script _including_ functions.
45    ///
46    /// This allows functions to be optimized based on dynamic global constants.
47    ///
48    /// # Example
49    ///
50    /// ```
51    /// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
52    /// use rhai::{Engine, Scope};
53    ///
54    /// let engine = Engine::new();
55    ///
56    /// // Create initialized scope
57    /// let mut scope = Scope::new();
58    /// scope.push("x", 40_i64);
59    ///
60    /// assert_eq!(engine.eval_with_scope::<i64>(&mut scope, "x += 2; x")?, 42);
61    /// assert_eq!(engine.eval_with_scope::<i64>(&mut scope, "x += 2; x")?, 44);
62    ///
63    /// // The variable in the scope is modified
64    /// assert_eq!(scope.get_value::<i64>("x").expect("variable x should exist"), 44);
65    /// # Ok(())
66    /// # }
67    /// ```
68    #[inline]
69    pub fn eval_with_scope<T: Variant + Clone>(
70        &self,
71        scope: &mut Scope,
72        script: &str,
73    ) -> RhaiResultOf<T> {
74        let ast = self.compile_scripts_with_scope_raw(
75            Some(scope),
76            [script],
77            #[cfg(not(feature = "no_optimize"))]
78            self.optimization_level,
79        )?;
80        self.eval_ast_with_scope(scope, &ast)
81    }
82    /// Evaluate a string containing an expression, returning the result value or an error.
83    ///
84    /// # Example
85    ///
86    /// ```
87    /// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
88    /// use rhai::Engine;
89    ///
90    /// let engine = Engine::new();
91    ///
92    /// assert_eq!(engine.eval_expression::<i64>("40 + 2")?, 42);
93    /// # Ok(())
94    /// # }
95    /// ```
96    #[inline(always)]
97    pub fn eval_expression<T: Variant + Clone>(&self, script: &str) -> RhaiResultOf<T> {
98        self.eval_expression_with_scope(&mut Scope::new(), script)
99    }
100    /// Evaluate a string containing an expression with own scope, returning the result value or an error.
101    ///
102    /// # Example
103    ///
104    /// ```
105    /// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
106    /// use rhai::{Engine, Scope};
107    ///
108    /// let engine = Engine::new();
109    ///
110    /// // Create initialized scope
111    /// let mut scope = Scope::new();
112    /// scope.push("x", 40_i64);
113    ///
114    /// assert_eq!(engine.eval_expression_with_scope::<i64>(&mut scope, "x + 2")?, 42);
115    /// # Ok(())
116    /// # }
117    /// ```
118    #[inline]
119    pub fn eval_expression_with_scope<T: Variant + Clone>(
120        &self,
121        scope: &mut Scope,
122        script: &str,
123    ) -> RhaiResultOf<T> {
124        let scripts = [script];
125        let ast = {
126            let (stream, tc) = self.lex(&scripts);
127
128            let input = &mut TokenStream::new(stream);
129            let lib = &mut <_>::default();
130            let state = ParseState::new(Some(scope), input, tc, lib);
131
132            // No need to optimize a lone expression
133            self.parse_global_expr(
134                state,
135                |_| {},
136                #[cfg(not(feature = "no_optimize"))]
137                crate::OptimizationLevel::None,
138            )?
139        };
140
141        self.eval_ast_with_scope(scope, &ast)
142    }
143    /// Evaluate an [`AST`], returning the result value or an error.
144    ///
145    /// # Example
146    ///
147    /// ```
148    /// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
149    /// use rhai::Engine;
150    ///
151    /// let engine = Engine::new();
152    ///
153    /// // Compile a script to an AST and store it for later evaluation
154    /// let ast = engine.compile("40 + 2")?;
155    ///
156    /// // Evaluate it
157    /// assert_eq!(engine.eval_ast::<i64>(&ast)?, 42);
158    /// # Ok(())
159    /// # }
160    /// ```
161    #[inline(always)]
162    pub fn eval_ast<T: Variant + Clone>(&self, ast: &AST) -> RhaiResultOf<T> {
163        self.eval_ast_with_scope(&mut Scope::new(), ast)
164    }
165    /// Evaluate an [`AST`] with own scope, returning the result value or an error.
166    ///
167    /// # Example
168    ///
169    /// ```
170    /// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
171    /// use rhai::{Engine, Scope};
172    ///
173    /// let engine = Engine::new();
174    ///
175    /// // Create initialized scope
176    /// let mut scope = Scope::new();
177    /// scope.push("x", 40_i64);
178    ///
179    /// // Compile a script to an AST and store it for later evaluation
180    /// let ast = engine.compile("x += 2; x")?;
181    ///
182    /// // Evaluate it
183    /// assert_eq!(engine.eval_ast_with_scope::<i64>(&mut scope, &ast)?, 42);
184    /// assert_eq!(engine.eval_ast_with_scope::<i64>(&mut scope, &ast)?, 44);
185    ///
186    /// // The variable in the scope is modified
187    /// assert_eq!(scope.get_value::<i64>("x").expect("variable x should exist"), 44);
188    /// # Ok(())
189    /// # }
190    /// ```
191    #[inline]
192    pub fn eval_ast_with_scope<T: Variant + Clone>(
193        &self,
194        scope: &mut Scope,
195        ast: &AST,
196    ) -> RhaiResultOf<T> {
197        let global = &mut self.new_global_runtime_state();
198        let caches = &mut Caches::new();
199
200        let result = self.eval_ast_with_scope_raw(global, caches, scope, ast)?;
201
202        // Bail out early if the return type needs no cast
203        if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
204            return Ok(reify! { result => T });
205        }
206
207        result.try_cast_result::<T>().map_err(|v| {
208            let typename = match type_name::<T>() {
209                typ if typ.contains("::") => self.map_type_name(typ),
210                typ => typ,
211            };
212
213            ERR::ErrorMismatchOutputType(
214                typename.into(),
215                self.map_type_name(v.type_name()).into(),
216                Position::NONE,
217            )
218            .into()
219        })
220    }
221    /// Evaluate an [`AST`] with own scope, returning the result value or an error.
222    #[inline]
223    pub(crate) fn eval_ast_with_scope_raw(
224        &self,
225        global: &mut GlobalRuntimeState,
226        caches: &mut Caches,
227        scope: &mut Scope,
228        ast: &AST,
229    ) -> RhaiResult {
230        let orig_source = mem::replace(&mut global.source, ast.source_raw().cloned());
231
232        #[cfg(not(feature = "no_function"))]
233        let orig_lib_len = global.lib.len();
234
235        #[cfg(not(feature = "no_function"))]
236        global.lib.push(ast.shared_lib().clone());
237
238        #[cfg(not(feature = "no_module"))]
239        let orig_embedded_module_resolver =
240            mem::replace(&mut global.embedded_module_resolver, ast.resolver.clone());
241
242        defer! { global => move |g| {
243            #[cfg(not(feature = "no_module"))]
244            {
245                g.embedded_module_resolver = orig_embedded_module_resolver;
246            }
247
248            #[cfg(not(feature = "no_function"))]
249            g.lib.truncate(orig_lib_len);
250
251            g.source = orig_source;
252        }}
253
254        let r = self.eval_global_statements(global, caches, scope, ast.statements(), true)?;
255
256        #[cfg(feature = "debugging")]
257        if self.is_debugger_registered() {
258            global.debugger_mut().status = crate::eval::DebuggerStatus::Terminate;
259            let node = &crate::ast::Stmt::Noop(Position::NONE);
260            self.dbg(global, caches, scope, None, node)?;
261        }
262
263        Ok(r)
264    }
265
266    /// Evaluate a binary operator with two operands with the [`Engine`].
267    ///
268    /// This method is very useful for simply comparing two [`Dynamic`] values --
269    /// simply use the `==` operator to compare them.
270    ///
271    /// # Example
272    ///
273    /// ```
274    /// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
275    /// use rhai::Engine;
276    ///
277    /// let engine = Engine::new();
278    ///
279    /// // Compare two values
280    /// let result = engine.eval_binary_op::<bool>("==", "abc", 123_i64)?;
281    /// assert!(!result);
282    /// # #[cfg(not(feature = "no_float"))]
283    /// # {
284    ///
285    /// // Rhai by default equates floating-point integers with normal integers.
286    /// let result = engine.eval_binary_op::<bool>("==", 123.0, 123_i64)?;
287    /// assert!(result);
288    /// # }
289    /// # Ok(())
290    /// # }
291    /// ```
292    #[inline(always)]
293    pub fn eval_binary_op<T: Variant + Clone>(
294        &self,
295        op: impl AsRef<str>,
296        lhs: impl Into<Dynamic>,
297        rhs: impl Into<Dynamic>,
298    ) -> RhaiResultOf<T> {
299        let lhs = lhs.into();
300        let lhs = Expr::DynamicConstant(lhs.into(), Position::NONE);
301        let rhs = rhs.into();
302        let rhs = Expr::DynamicConstant(rhs.into(), Position::NONE);
303
304        let op = op.as_ref();
305
306        self.make_function_call(
307            &mut self.new_global_runtime_state(),
308            &mut Caches::new(),
309            &mut Scope::new(),
310            None,
311            op,
312            Token::lookup_symbol_from_syntax(op).as_ref(),
313            Some(&lhs),
314            &[rhs],
315            FnCallHashes::from_native_only(calc_fn_hash(None, op, 2)),
316            false,
317            Position::NONE,
318        )?
319        .try_cast_result::<T>()
320        .map_err(|v| {
321            let typename = match type_name::<T>() {
322                typ if typ.contains("::") => self.map_type_name(typ),
323                typ => typ,
324            };
325
326            ERR::ErrorMismatchOutputType(
327                typename.into(),
328                self.map_type_name(v.type_name()).into(),
329                Position::NONE,
330            )
331            .into()
332        })
333    }
334    /// Evaluate an arbitrary function call in the [`Engine`].
335    #[inline(always)]
336    pub fn eval_fn_call<T: Variant + Clone>(
337        &self,
338        fn_name: impl AsRef<str>,
339        this_ptr: Option<&mut Dynamic>,
340        args: impl FuncArgs,
341    ) -> RhaiResultOf<T> {
342        let mut has_this = false;
343        let arg_values = &mut FnArgsVec::new_const();
344        args.parse(arg_values);
345        let args = &mut arg_values.iter_mut().collect::<FnArgsVec<_>>();
346
347        if let Some(this_ptr) = this_ptr {
348            args.insert(0, this_ptr);
349            has_this = true;
350        }
351
352        self.eval_fn_call_with_arguments::<T>(fn_name, args, has_this, has_this)?
353            .try_cast_result::<T>()
354            .map_err(|v| {
355                let typename = match type_name::<T>() {
356                    typ if typ.contains("::") => self.map_type_name(typ),
357                    typ => typ,
358                };
359
360                ERR::ErrorMismatchOutputType(
361                    typename.into(),
362                    self.map_type_name(v.type_name()).into(),
363                    Position::NONE,
364                )
365                .into()
366            })
367    }
368    /// Evaluate a function call with the [`Engine`].
369    pub(crate) fn eval_fn_call_with_arguments<T: Variant + Clone>(
370        &self,
371        fn_name: impl AsRef<str>,
372        args: &mut [&mut Dynamic],
373        is_ref_mut: bool,
374        is_method_call: bool,
375    ) -> RhaiResult {
376        let name = fn_name.as_ref();
377        let op_token = Token::lookup_symbol_from_syntax(name);
378
379        let hashes = if is_method_call {
380            #[cfg(feature = "no_function")]
381            {
382                panic!("method calls are not supported under `no_function`")
383            }
384            #[cfg(not(feature = "no_function"))]
385            {
386                FnCallHashes::from_script_and_native(
387                    calc_fn_hash(None, name, args.len() - 1),
388                    calc_fn_hash(None, name, args.len()),
389                )
390            }
391        } else {
392            FnCallHashes::from_hash(calc_fn_hash(None, name, args.len()))
393        };
394
395        self.exec_fn_call(
396            &mut self.new_global_runtime_state(),
397            &mut Caches::new(),
398            None,
399            name,
400            op_token.as_ref(),
401            hashes,
402            args,
403            is_ref_mut,
404            is_method_call,
405            Position::NONE,
406        )
407        .map(|(v, ..)| v)
408    }
409}
410
411/// Evaluate a string as a script, returning the result value or an error.
412///
413/// # Example
414///
415/// ```
416/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
417/// let result: i64 = rhai::eval("40 + 2")?;
418///
419/// assert_eq!(result, 42);
420/// # Ok(())
421/// # }
422/// ```
423#[inline(always)]
424pub fn eval<T: Variant + Clone>(script: &str) -> RhaiResultOf<T> {
425    Engine::new().eval(script)
426}