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}