From 344b48ad366859373270f1ed736651af9e099568 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 20 Nov 2021 14:57:21 +0800 Subject: [PATCH] Split APIs into files. --- src/api/call_fn.rs | 195 ++++ src/api/compile.rs | 412 ++++++++ src/api/eval.rs | 232 +++++ src/api/events.rs | 265 +++++ src/api/files.rs | 201 ++++ src/api/limits.rs | 174 ++++ src/api/mod.rs | 214 +++- src/api/public.rs | 2365 ------------------------------------------- src/api/register.rs | 999 ++++++++++++++++++ src/api/run.rs | 67 ++ src/api/settings.rs | 334 ------ 11 files changed, 2757 insertions(+), 2701 deletions(-) create mode 100644 src/api/call_fn.rs create mode 100644 src/api/compile.rs create mode 100644 src/api/eval.rs create mode 100644 src/api/events.rs create mode 100644 src/api/files.rs create mode 100644 src/api/limits.rs delete mode 100644 src/api/public.rs create mode 100644 src/api/register.rs create mode 100644 src/api/run.rs delete mode 100644 src/api/settings.rs diff --git a/src/api/call_fn.rs b/src/api/call_fn.rs new file mode 100644 index 00000000..61309f18 --- /dev/null +++ b/src/api/call_fn.rs @@ -0,0 +1,195 @@ +//! Module that defines the `call_fn` API of [`Engine`]. + +use crate::engine::{EvalState, Imports}; +use crate::types::dynamic::Variant; +use crate::{Dynamic, Engine, EvalAltResult, Position, RhaiResult, Scope, AST}; +use std::any::type_name; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +impl Engine { + /// Call a script function defined in an [`AST`] with multiple arguments. + /// Arguments are passed as a tuple. + /// + /// Not available under `no_function`. + /// + /// The [`AST`] is evaluated before calling the function. + /// This allows a script to load the necessary modules. + /// This is usually desired. If not, a specialized [`AST`] can be prepared that contains only + /// function definitions without any body script via [`AST::clear_statements`]. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_function"))] + /// # { + /// use rhai::{Engine, Scope}; + /// + /// let engine = Engine::new(); + /// + /// let ast = engine.compile(" + /// fn add(x, y) { len(x) + y + foo } + /// fn add1(x) { len(x) + 1 + foo } + /// fn bar() { foo/2 } + /// ")?; + /// + /// let mut scope = Scope::new(); + /// scope.push("foo", 42_i64); + /// + /// // Call the script-defined function + /// let result: i64 = engine.call_fn(&mut scope, &ast, "add", ( "abc", 123_i64 ) )?; + /// assert_eq!(result, 168); + /// + /// let result: i64 = engine.call_fn(&mut scope, &ast, "add1", ( "abc", ) )?; + /// // ^^^^^^^^^^ tuple of one + /// assert_eq!(result, 46); + /// + /// let result: i64 = engine.call_fn(&mut scope, &ast, "bar", () )?; + /// assert_eq!(result, 21); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_function"))] + #[inline] + pub fn call_fn( + &self, + scope: &mut Scope, + ast: &AST, + name: impl AsRef, + args: impl crate::FuncArgs, + ) -> Result> { + let mut arg_values = crate::StaticVec::new(); + args.parse(&mut arg_values); + + let result = self.call_fn_raw(scope, ast, true, true, name, None, arg_values)?; + + let typ = self.map_type_name(result.type_name()); + + result.try_cast().ok_or_else(|| { + EvalAltResult::ErrorMismatchOutputType( + self.map_type_name(type_name::()).into(), + typ.into(), + Position::NONE, + ) + .into() + }) + } + /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments + /// and the following options: + /// + /// * whether to evaluate the [`AST`] to load necessary modules before calling the function + /// * whether to rewind the [`Scope`] after the function call + /// * a value for binding to the `this` pointer (if any) + /// + /// Not available under `no_function`. + /// + /// # WARNING + /// + /// All the arguments are _consumed_, meaning that they're replaced by `()`. + /// This is to avoid unnecessarily cloning the arguments. + /// Do not use the arguments after this call. If they are needed afterwards, + /// clone them _before_ calling this function. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_function"))] + /// # { + /// use rhai::{Engine, Scope, Dynamic}; + /// + /// let engine = Engine::new(); + /// + /// let ast = engine.compile(" + /// fn add(x, y) { len(x) + y + foo } + /// fn add1(x) { len(x) + 1 + foo } + /// fn bar() { foo/2 } + /// fn action(x) { this += x; } // function using 'this' pointer + /// fn decl(x) { let hello = x; } // declaring variables + /// ")?; + /// + /// let mut scope = Scope::new(); + /// scope.push("foo", 42_i64); + /// + /// // Call the script-defined function + /// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "add", None, [ "abc".into(), 123_i64.into() ])?; + /// // ^^^^ no 'this' pointer + /// assert_eq!(result.cast::(), 168); + /// + /// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "add1", None, [ "abc".into() ])?; + /// assert_eq!(result.cast::(), 46); + /// + /// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "bar", None, [])?; + /// assert_eq!(result.cast::(), 21); + /// + /// let mut value: Dynamic = 1_i64.into(); + /// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "action", Some(&mut value), [ 41_i64.into() ])?; + /// // ^^^^^^^^^^^^^^^^ binding the 'this' pointer + /// assert_eq!(value.as_int().expect("value should be INT"), 42); + /// + /// engine.call_fn_raw(&mut scope, &ast, true, false, "decl", None, [ 42_i64.into() ])?; + /// // ^^^^^ do not rewind scope + /// assert_eq!(scope.get_value::("hello").unwrap(), 42); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_function"))] + #[inline] + pub fn call_fn_raw( + &self, + scope: &mut Scope, + ast: &AST, + eval_ast: bool, + rewind_scope: bool, + name: impl AsRef, + this_ptr: Option<&mut Dynamic>, + arg_values: impl AsMut<[Dynamic]>, + ) -> RhaiResult { + let state = &mut EvalState::new(); + let mods = &mut Imports::new(); + let statements = ast.statements(); + + let orig_scope_len = scope.len(); + + if eval_ast && !statements.is_empty() { + // Make sure new variables introduced at global level do not _spill_ into the function call + self.eval_global_statements(scope, mods, state, statements, &[ast.lib()], 0)?; + + if rewind_scope { + scope.rewind(orig_scope_len); + } + } + + let name = name.as_ref(); + let mut this_ptr = this_ptr; + let mut arg_values = arg_values; + let mut args: crate::StaticVec<_> = arg_values.as_mut().iter_mut().collect(); + + let fn_def = ast + .lib() + .get_script_fn(name, args.len()) + .ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::NONE))?; + + // Check for data race. + #[cfg(not(feature = "no_closure"))] + crate::func::call::ensure_no_data_race(name, &mut args, false)?; + + let result = self.call_script_fn( + scope, + mods, + state, + &[ast.lib()], + &mut this_ptr, + fn_def, + &mut args, + Position::NONE, + rewind_scope, + 0, + ); + + result + } +} diff --git a/src/api/compile.rs b/src/api/compile.rs new file mode 100644 index 00000000..abf23900 --- /dev/null +++ b/src/api/compile.rs @@ -0,0 +1,412 @@ +//! Module that defines the public compilation API of [`Engine`]. + +use crate::parser::ParseState; +use crate::{Engine, EvalAltResult, Identifier, ParseError, Position, Scope, AST}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +#[cfg(not(feature = "no_object"))] +use crate::Map; + +impl Engine { + /// Compile a string into an [`AST`], which can be used later for evaluation. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// // Compile a script to an AST and store it for later evaluation + /// let ast = engine.compile("40 + 2")?; + /// + /// for _ in 0..42 { + /// assert_eq!(engine.eval_ast::(&ast)?, 42); + /// } + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn compile(&self, script: &str) -> Result { + self.compile_with_scope(&Scope::new(), script) + } + /// Compile a string into an [`AST`] using own scope, which can be used later for evaluation. + /// + /// ## Constants Propagation + /// + /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated + /// throughout the script _including_ functions. This allows functions to be optimized based on + /// dynamic global constants. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_optimize"))] + /// # { + /// use rhai::{Engine, Scope, OptimizationLevel}; + /// + /// let mut engine = Engine::new(); + /// + /// // Create initialized scope + /// let mut scope = Scope::new(); + /// scope.push_constant("x", 42_i64); // 'x' is a constant + /// + /// // Compile a script to an AST and store it for later evaluation. + /// // Notice that `Full` optimization is on, so constants are folded + /// // into function calls and operators. + /// let ast = engine.compile_with_scope(&mut scope, + /// "if x > 40 { x } else { 0 }" // all 'x' are replaced with 42 + /// )?; + /// + /// // Normally this would have failed because no scope is passed into the 'eval_ast' + /// // call and so the variable 'x' does not exist. Here, it passes because the script + /// // has been optimized and all references to 'x' are already gone. + /// assert_eq!(engine.eval_ast::(&ast)?, 42); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn compile_with_scope(&self, scope: &Scope, script: &str) -> Result { + self.compile_scripts_with_scope(scope, &[script]) + } + /// Compile a string into an [`AST`] using own scope, which can be used later for evaluation, + /// embedding all imported modules. + /// + /// Not available under `no_module`. + /// + /// Modules referred by `import` statements containing literal string paths are eagerly resolved + /// via the current [module resolver][crate::ModuleResolver] and embedded into the resultant + /// [`AST`]. When it is evaluated later, `import` statement directly recall pre-resolved + /// [modules][Module] and the resolution process is not performed again. + #[cfg(not(feature = "no_module"))] + pub fn compile_into_self_contained( + &self, + scope: &Scope, + script: &str, + ) -> Result> { + use crate::{ + ast::{ASTNode, Expr, Stmt}, + func::native::shared_take_or_clone, + module::resolvers::StaticModuleResolver, + }; + use std::collections::BTreeSet; + + fn collect_imports( + ast: &AST, + resolver: &StaticModuleResolver, + imports: &mut BTreeSet, + ) { + ast.walk( + &mut |path| match path.last().expect("contains current node") { + // Collect all `import` statements with a string constant path + ASTNode::Stmt(Stmt::Import(Expr::StringConstant(s, _), _, _)) + if !resolver.contains_path(s) && !imports.contains(s.as_str()) => + { + imports.insert(s.clone().into()); + true + } + _ => true, + }, + ); + } + + let mut ast = self.compile_scripts_with_scope(scope, &[script])?; + + if let Some(ref module_resolver) = self.module_resolver { + let mut resolver = StaticModuleResolver::new(); + let mut imports = BTreeSet::new(); + + collect_imports(&ast, &resolver, &mut imports); + + if !imports.is_empty() { + while let Some(path) = imports.iter().next() { + let path = path.clone(); + + match module_resolver.resolve_ast(self, None, &path, Position::NONE) { + Some(Ok(module_ast)) => { + collect_imports(&module_ast, &resolver, &mut imports) + } + Some(err) => return err, + None => (), + } + + let module = module_resolver.resolve(self, None, &path, Position::NONE)?; + let module = shared_take_or_clone(module); + + imports.remove(&path); + resolver.insert(path, module); + } + ast.set_resolver(resolver); + } + } + + Ok(ast) + } + /// When passed a list of strings, first join the strings into one large script, + /// and then compile them into an [`AST`] using own scope, which can be used later for evaluation. + /// + /// The scope is useful for passing constants into the script for optimization + /// when using [`OptimizationLevel::Full`]. + /// + /// ## Note + /// + /// All strings are simply parsed one after another with nothing inserted in between, not even + /// a newline or space. + /// + /// ## Constants Propagation + /// + /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated + /// throughout the script _including_ functions. This allows functions to be optimized based on + /// dynamic global constants. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_optimize"))] + /// # { + /// use rhai::{Engine, Scope, OptimizationLevel}; + /// + /// let mut engine = Engine::new(); + /// + /// // Create initialized scope + /// let mut scope = Scope::new(); + /// scope.push_constant("x", 42_i64); // 'x' is a constant + /// + /// // Compile a script made up of script segments to an AST and store it for later evaluation. + /// // Notice that `Full` optimization is on, so constants are folded + /// // into function calls and operators. + /// let ast = engine.compile_scripts_with_scope(&mut scope, &[ + /// "if x > 40", // all 'x' are replaced with 42 + /// "{ x } el", + /// "se { 0 }" // segments do not need to be valid scripts! + /// ])?; + /// + /// // Normally this would have failed because no scope is passed into the 'eval_ast' + /// // call and so the variable 'x' does not exist. Here, it passes because the script + /// // has been optimized and all references to 'x' are already gone. + /// assert_eq!(engine.eval_ast::(&ast)?, 42); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn compile_scripts_with_scope( + &self, + scope: &Scope, + scripts: &[&str], + ) -> Result { + self.compile_with_scope_and_optimization_level( + scope, + scripts, + #[cfg(not(feature = "no_optimize"))] + self.optimization_level, + ) + } + /// Join a list of strings and compile into an [`AST`] using own scope at a specific optimization level. + /// + /// ## Constants Propagation + /// + /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated + /// throughout the script _including_ functions. This allows functions to be optimized based on + /// dynamic global constants. + #[inline] + pub(crate) fn compile_with_scope_and_optimization_level( + &self, + scope: &Scope, + scripts: &[&str], + #[cfg(not(feature = "no_optimize"))] optimization_level: crate::OptimizationLevel, + ) -> Result { + let (stream, tokenizer_control) = + self.lex_raw(scripts, self.token_mapper.as_ref().map(Box::as_ref)); + let mut state = ParseState::new(self, tokenizer_control); + self.parse( + &mut stream.peekable(), + &mut state, + scope, + #[cfg(not(feature = "no_optimize"))] + optimization_level, + ) + } + /// Compile a string containing an expression into an [`AST`], + /// which can be used later for evaluation. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// // Compile a script to an AST and store it for later evaluation + /// let ast = engine.compile_expression("40 + 2")?; + /// + /// for _ in 0..42 { + /// assert_eq!(engine.eval_ast::(&ast)?, 42); + /// } + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn compile_expression(&self, script: &str) -> Result { + self.compile_expression_with_scope(&Scope::new(), script) + } + /// Compile a string containing an expression into an [`AST`] using own scope, + /// which can be used later for evaluation. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_optimize"))] + /// # { + /// use rhai::{Engine, Scope, OptimizationLevel}; + /// + /// let mut engine = Engine::new(); + /// + /// // Create initialized scope + /// let mut scope = Scope::new(); + /// scope.push_constant("x", 10_i64); // 'x' is a constant + /// + /// // Compile a script to an AST and store it for later evaluation. + /// // Notice that `Full` optimization is on, so constants are folded + /// // into function calls and operators. + /// let ast = engine.compile_expression_with_scope(&mut scope, + /// "2 + (x + x) * 2" // all 'x' are replaced with 10 + /// )?; + /// + /// // Normally this would have failed because no scope is passed into the 'eval_ast' + /// // call and so the variable 'x' does not exist. Here, it passes because the script + /// // has been optimized and all references to 'x' are already gone. + /// assert_eq!(engine.eval_ast::(&ast)?, 42); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn compile_expression_with_scope( + &self, + scope: &Scope, + script: &str, + ) -> Result { + let scripts = [script]; + let (stream, tokenizer_control) = + self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); + + let mut peekable = stream.peekable(); + let mut state = ParseState::new(self, tokenizer_control); + self.parse_global_expr( + &mut peekable, + &mut state, + scope, + #[cfg(not(feature = "no_optimize"))] + self.optimization_level, + ) + } + /// Parse a JSON string into an [object map][`Map`]. + /// This is a light-weight alternative to using, say, + /// [`serde_json`](https://crates.io/crates/serde_json) to deserialize the JSON. + /// + /// Not available under `no_object`. + /// + /// The JSON string must be an object hash. It cannot be a simple scalar value. + /// + /// Set `has_null` to `true` in order to map `null` values to `()`. + /// Setting it to `false` will cause an [`ErrorVariableNotFound`][EvalAltResult::ErrorVariableNotFound] error during parsing. + /// + /// # JSON With Sub-Objects + /// + /// This method assumes no sub-objects in the JSON string. That is because the syntax + /// of a JSON sub-object (or object hash), `{ .. }`, is different from Rhai's syntax, `#{ .. }`. + /// Parsing a JSON string with sub-objects will cause a syntax error. + /// + /// If it is certain that the character `{` never appears in any text string within the JSON object, + /// which is a valid assumption for many use cases, then globally replace `{` with `#{` before calling this method. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::{Engine, Map}; + /// + /// let engine = Engine::new(); + /// + /// let map = engine.parse_json( + /// r#"{"a":123, "b":42, "c":{"x":false, "y":true}, "d":null}"# + /// .replace("{", "#{").as_str(), + /// true)?; + /// + /// assert_eq!(map.len(), 4); + /// assert_eq!(map["a"].as_int().expect("a should exist"), 123); + /// assert_eq!(map["b"].as_int().expect("b should exist"), 42); + /// assert!(map["d"].is::<()>()); + /// + /// let c = map["c"].read_lock::().expect("c should exist"); + /// assert_eq!(c["x"].as_bool().expect("x should be bool"), false); + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_object"))] + #[inline(always)] + pub fn parse_json( + &self, + json: impl AsRef, + has_null: bool, + ) -> Result> { + use crate::tokenizer::Token; + + fn parse_json_inner( + engine: &Engine, + json: &str, + has_null: bool, + ) -> Result> { + let mut scope = Scope::new(); + let json_text = json.trim_start(); + let scripts = if json_text.starts_with(Token::MapStart.literal_syntax()) { + [json_text, ""] + } else if json_text.starts_with(Token::LeftBrace.literal_syntax()) { + ["#", json_text] + } else { + return Err(crate::ParseErrorType::MissingToken( + Token::LeftBrace.syntax().into(), + "to start a JSON object hash".into(), + ) + .into_err(Position::new(1, (json.len() - json_text.len() + 1) as u16)) + .into()); + }; + let (stream, tokenizer_control) = engine.lex_raw( + &scripts, + if has_null { + Some(&|token, _, _| { + match token { + // If `null` is present, make sure `null` is treated as a variable + Token::Reserved(s) if &*s == "null" => Token::Identifier(s), + _ => token, + } + }) + } else { + None + }, + ); + let mut state = ParseState::new(engine, tokenizer_control); + let ast = engine.parse_global_expr( + &mut stream.peekable(), + &mut state, + &scope, + #[cfg(not(feature = "no_optimize"))] + crate::OptimizationLevel::None, + )?; + if has_null { + scope.push_constant("null", ()); + } + engine.eval_ast_with_scope(&mut scope, &ast) + } + + parse_json_inner(self, json.as_ref(), has_null) + } +} diff --git a/src/api/eval.rs b/src/api/eval.rs new file mode 100644 index 00000000..def17861 --- /dev/null +++ b/src/api/eval.rs @@ -0,0 +1,232 @@ +//! Module that defines the public evaluation API of [`Engine`]. + +use crate::engine::{EvalState, Imports}; +use crate::parser::ParseState; +use crate::types::dynamic::Variant; +use crate::{Dynamic, Engine, EvalAltResult, Position, RhaiResult, Scope, AST}; +use std::any::type_name; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +impl Engine { + /// Evaluate a string. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// assert_eq!(engine.eval::("40 + 2")?, 42); + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn eval(&self, script: &str) -> Result> { + self.eval_with_scope(&mut Scope::new(), script) + } + /// Evaluate a string with own scope. + /// + /// ## Constants Propagation + /// + /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated + /// throughout the script _including_ functions. This allows functions to be optimized based on + /// dynamic global constants. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::{Engine, Scope}; + /// + /// let engine = Engine::new(); + /// + /// // Create initialized scope + /// let mut scope = Scope::new(); + /// scope.push("x", 40_i64); + /// + /// assert_eq!(engine.eval_with_scope::(&mut scope, "x += 2; x")?, 42); + /// assert_eq!(engine.eval_with_scope::(&mut scope, "x += 2; x")?, 44); + /// + /// // The variable in the scope is modified + /// assert_eq!(scope.get_value::("x").expect("variable x should exist"), 44); + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn eval_with_scope( + &self, + scope: &mut Scope, + script: &str, + ) -> Result> { + let ast = self.compile_with_scope_and_optimization_level( + scope, + &[script], + #[cfg(not(feature = "no_optimize"))] + self.optimization_level, + )?; + self.eval_ast_with_scope(scope, &ast) + } + /// Evaluate a string containing an expression. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// assert_eq!(engine.eval_expression::("40 + 2")?, 42); + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn eval_expression( + &self, + script: &str, + ) -> Result> { + self.eval_expression_with_scope(&mut Scope::new(), script) + } + /// Evaluate a string containing an expression with own scope. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::{Engine, Scope}; + /// + /// let engine = Engine::new(); + /// + /// // Create initialized scope + /// let mut scope = Scope::new(); + /// scope.push("x", 40_i64); + /// + /// assert_eq!(engine.eval_expression_with_scope::(&mut scope, "x + 2")?, 42); + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn eval_expression_with_scope( + &self, + scope: &mut Scope, + script: &str, + ) -> Result> { + let scripts = [script]; + let (stream, tokenizer_control) = + self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); + let mut state = ParseState::new(self, tokenizer_control); + + // No need to optimize a lone expression + let ast = self.parse_global_expr( + &mut stream.peekable(), + &mut state, + scope, + #[cfg(not(feature = "no_optimize"))] + crate::OptimizationLevel::None, + )?; + + self.eval_ast_with_scope(scope, &ast) + } + /// Evaluate an [`AST`]. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// // Compile a script to an AST and store it for later evaluation + /// let ast = engine.compile("40 + 2")?; + /// + /// // Evaluate it + /// assert_eq!(engine.eval_ast::(&ast)?, 42); + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn eval_ast(&self, ast: &AST) -> Result> { + self.eval_ast_with_scope(&mut Scope::new(), ast) + } + /// Evaluate an [`AST`] with own scope. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::{Engine, Scope}; + /// + /// let engine = Engine::new(); + /// + /// // Compile a script to an AST and store it for later evaluation + /// let ast = engine.compile("x + 2")?; + /// + /// // Create initialized scope + /// let mut scope = Scope::new(); + /// scope.push("x", 40_i64); + /// + /// // Compile a script to an AST and store it for later evaluation + /// let ast = engine.compile("x += 2; x")?; + /// + /// // Evaluate it + /// assert_eq!(engine.eval_ast_with_scope::(&mut scope, &ast)?, 42); + /// assert_eq!(engine.eval_ast_with_scope::(&mut scope, &ast)?, 44); + /// + /// // The variable in the scope is modified + /// assert_eq!(scope.get_value::("x").expect("variable x should exist"), 44); + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn eval_ast_with_scope( + &self, + scope: &mut Scope, + ast: &AST, + ) -> Result> { + let mods = &mut Imports::new(); + + let result = self.eval_ast_with_scope_raw(scope, mods, ast, 0)?; + + let typ = self.map_type_name(result.type_name()); + + result.try_cast::().ok_or_else(|| { + EvalAltResult::ErrorMismatchOutputType( + self.map_type_name(type_name::()).into(), + typ.into(), + Position::NONE, + ) + .into() + }) + } + /// Evaluate an [`AST`] with own scope. + #[inline] + pub(crate) fn eval_ast_with_scope_raw<'a>( + &self, + scope: &mut Scope, + mods: &mut Imports, + ast: &'a AST, + level: usize, + ) -> RhaiResult { + let mut state = EvalState::new(); + if ast.source_raw().is_some() { + mods.source = ast.source_raw().cloned(); + } + #[cfg(not(feature = "no_module"))] + { + mods.embedded_module_resolver = ast.resolver(); + } + + let statements = ast.statements(); + + if statements.is_empty() { + return Ok(Dynamic::UNIT); + } + + let lib = &[ast.lib()]; + self.eval_global_statements(scope, mods, &mut state, statements, lib, level) + } +} diff --git a/src/api/events.rs b/src/api/events.rs new file mode 100644 index 00000000..630e241c --- /dev/null +++ b/src/api/events.rs @@ -0,0 +1,265 @@ +//! Module that defines public event handlers for [`Engine`]. + +use crate::engine::EvalContext; +use crate::func::SendSync; +use crate::{Dynamic, Engine, EvalAltResult, Position}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +impl Engine { + /// Provide a callback that will be invoked before each variable access. + /// + /// # Callback Function Signature + /// + /// The callback function signature takes the following form: + /// + /// > `Fn(name: &str, index: usize, context: &EvalContext)` + /// > ` -> Result, Box> + 'static` + /// + /// where: + /// * `index`: an offset from the bottom of the current [`Scope`] that the variable is supposed + /// to reside. Offsets start from 1, with 1 meaning the last variable in the current + /// [`Scope`]. Essentially the correct variable is at position `scope.len() - index`. + /// If `index` is zero, then there is no pre-calculated offset position and a search through the + /// current [`Scope`] must be performed. + /// + /// * `context`: the current [evaluation context][`EvalContext`]. + /// + /// ## Return value + /// + /// * `Ok(None)`: continue with normal variable access. + /// * `Ok(Some(Dynamic))`: the variable's value. + /// + /// ## Raising errors + /// + /// Return `Err(...)` if there is an error. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// // Register a variable resolver. + /// engine.on_var(|name, _, _| { + /// match name { + /// "MYSTIC_NUMBER" => Ok(Some(42_i64.into())), + /// _ => Ok(None) + /// } + /// }); + /// + /// engine.eval::("MYSTIC_NUMBER")?; + /// + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn on_var( + &mut self, + callback: impl Fn(&str, usize, &EvalContext) -> Result, Box> + + SendSync + + 'static, + ) -> &mut Self { + self.resolve_var = Some(Box::new(callback)); + self + } + /// _(internals)_ Provide a callback that will be invoked during parsing to remap certain tokens. + /// Exported under the `internals` feature only. + /// + /// # Callback Function Signature + /// + /// The callback function signature takes the following form: + /// + /// > `Fn(token: Token, pos: Position, state: &TokenizeState) -> Token` + /// + /// where: + /// * [`token`][crate::tokenizer::Token]: current token parsed + /// * [`pos`][`Position`]: location of the token + /// * [`state`][crate::tokenizer::TokenizeState]: current state of the tokenizer + /// + /// ## Raising errors + /// + /// It is possible to raise a parsing error by returning + /// [`Token::LexError`][crate::tokenizer::Token::LexError] as the mapped token. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::{Engine, Token}; + /// + /// let mut engine = Engine::new(); + /// + /// // Register a token mapper. + /// engine.on_parse_token(|token, _, _| { + /// match token { + /// // Convert all integer literals to strings + /// Token::IntegerConstant(n) => Token::StringConstant(n.to_string().into()), + /// // Convert 'begin' .. 'end' to '{' .. '}' + /// Token::Identifier(s) if &*s == "begin" => Token::LeftBrace, + /// Token::Identifier(s) if &*s == "end" => Token::RightBrace, + /// // Pass through all other tokens unchanged + /// _ => token + /// } + /// }); + /// + /// assert_eq!(engine.eval::("42")?, "42"); + /// assert_eq!(engine.eval::("true")?, true); + /// assert_eq!(engine.eval::("let x = 42; begin let x = 0; end; x")?, "42"); + /// + /// # Ok(()) + /// # } + /// ``` + #[cfg(feature = "internals")] + #[inline(always)] + pub fn on_parse_token( + &mut self, + callback: impl Fn( + crate::tokenizer::Token, + Position, + &crate::tokenizer::TokenizeState, + ) -> crate::tokenizer::Token + + SendSync + + 'static, + ) -> &mut Self { + self.token_mapper = Some(Box::new(callback)); + self + } + /// Register a callback for script evaluation progress. + /// + /// Not available under `unchecked`. + /// + /// # Callback Function Signature + /// + /// The callback function signature takes the following form: + /// + /// > `Fn(counter: u64) -> Option` + /// + /// ## Return value + /// + /// * `None`: continue running the script. + /// * `Some(Dynamic)`: terminate the script with the specified exception value. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # use std::sync::RwLock; + /// # use std::sync::Arc; + /// use rhai::Engine; + /// + /// let result = Arc::new(RwLock::new(0_u64)); + /// let logger = result.clone(); + /// + /// let mut engine = Engine::new(); + /// + /// engine.on_progress(move |ops| { + /// if ops > 10000 { + /// Some("Over 10,000 operations!".into()) + /// } else if ops % 800 == 0 { + /// *logger.write().unwrap() = ops; + /// None + /// } else { + /// None + /// } + /// }); + /// + /// engine.run("for x in range(0, 50000) {}") + /// .expect_err("should error"); + /// + /// assert_eq!(*result.read().unwrap(), 9600); + /// + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "unchecked"))] + #[inline(always)] + pub fn on_progress( + &mut self, + callback: impl Fn(u64) -> Option + SendSync + 'static, + ) -> &mut Self { + self.progress = Some(Box::new(callback)); + self + } + /// Override default action of `print` (print to stdout using [`println!`]) + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # use std::sync::RwLock; + /// # use std::sync::Arc; + /// use rhai::Engine; + /// + /// let result = Arc::new(RwLock::new(String::new())); + /// + /// let mut engine = Engine::new(); + /// + /// // Override action of 'print' function + /// let logger = result.clone(); + /// engine.on_print(move |s| logger.write().unwrap().push_str(s)); + /// + /// engine.run("print(40 + 2);")?; + /// + /// assert_eq!(*result.read().unwrap(), "42"); + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn on_print(&mut self, callback: impl Fn(&str) + SendSync + 'static) -> &mut Self { + self.print = Some(Box::new(callback)); + self + } + /// Override default action of `debug` (print to stdout using [`println!`]) + /// + /// # Callback Function Signature + /// + /// The callback function signature passed takes the following form: + /// + /// > `Fn(text: &str, source: Option<&str>, pos: Position)` + /// + /// where: + /// * `text`: the text to display + /// * `source`: current source, if any + /// * [`pos`][`Position`]: location of the `debug` call + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # use std::sync::RwLock; + /// # use std::sync::Arc; + /// use rhai::Engine; + /// + /// let result = Arc::new(RwLock::new(String::new())); + /// + /// let mut engine = Engine::new(); + /// + /// // Override action of 'print' function + /// let logger = result.clone(); + /// engine.on_debug(move |s, src, pos| logger.write().unwrap().push_str( + /// &format!("{} @ {:?} > {}", src.unwrap_or("unknown"), pos, s) + /// )); + /// + /// let mut ast = engine.compile(r#"let x = "hello"; debug(x);"#)?; + /// ast.set_source("world"); + /// engine.run_ast(&ast)?; + /// + /// #[cfg(not(feature = "no_position"))] + /// assert_eq!(*result.read().unwrap(), r#"world @ 1:18 > "hello""#); + /// #[cfg(feature = "no_position")] + /// assert_eq!(*result.read().unwrap(), r#"world @ none > "hello""#); + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn on_debug( + &mut self, + callback: impl Fn(&str, Option<&str>, Position) + SendSync + 'static, + ) -> &mut Self { + self.debug = Some(Box::new(callback)); + self + } +} diff --git a/src/api/files.rs b/src/api/files.rs new file mode 100644 index 00000000..70bcff02 --- /dev/null +++ b/src/api/files.rs @@ -0,0 +1,201 @@ +//! Module that defines the public file-based API of [`Engine`]. +#![cfg(not(feature = "no_std"))] +#![cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] + +use crate::types::dynamic::Variant; +use crate::{Engine, EvalAltResult, Scope, AST}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +impl Engine { + /// Read the contents of a file into a string. + fn read_file(path: std::path::PathBuf) -> Result> { + use std::io::Read; + + let mut f = std::fs::File::open(path.clone()).map_err(|err| { + EvalAltResult::ErrorSystem( + format!("Cannot open script file '{}'", path.to_string_lossy()), + err.into(), + ) + })?; + + let mut contents = String::new(); + + f.read_to_string(&mut contents).map_err(|err| { + EvalAltResult::ErrorSystem( + format!("Cannot read script file '{}'", path.to_string_lossy()), + err.into(), + ) + })?; + + if contents.starts_with("#!") { + // Remove shebang + if let Some(n) = contents.find('\n') { + contents.drain(0..n).count(); + } else { + contents.clear(); + } + }; + + Ok(contents) + } + /// Compile a script file into an [`AST`], which can be used later for evaluation. + /// + /// Not available under `no_std` or `WASM`. + /// + /// # Example + /// + /// ```no_run + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// // Compile a script file to an AST and store it for later evaluation. + /// // Notice that a PathBuf is required which can easily be constructed from a string. + /// let ast = engine.compile_file("script.rhai".into())?; + /// + /// for _ in 0..42 { + /// engine.eval_ast::(&ast)?; + /// } + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn compile_file(&self, path: std::path::PathBuf) -> Result> { + self.compile_file_with_scope(&Scope::new(), path) + } + /// Compile a script file into an [`AST`] using own scope, which can be used later for evaluation. + /// + /// Not available under `no_std` or `WASM`. + /// + /// ## Constants Propagation + /// + /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated + /// throughout the script _including_ functions. This allows functions to be optimized based on + /// dynamic global constants. + /// + /// # Example + /// + /// ```no_run + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_optimize"))] + /// # { + /// use rhai::{Engine, Scope, OptimizationLevel}; + /// + /// let mut engine = Engine::new(); + /// + /// // Create initialized scope + /// let mut scope = Scope::new(); + /// scope.push_constant("x", 42_i64); // 'x' is a constant + /// + /// // Compile a script to an AST and store it for later evaluation. + /// // Notice that a PathBuf is required which can easily be constructed from a string. + /// let ast = engine.compile_file_with_scope(&mut scope, "script.rhai".into())?; + /// + /// let result = engine.eval_ast::(&ast)?; + /// # } + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn compile_file_with_scope( + &self, + scope: &Scope, + path: std::path::PathBuf, + ) -> Result> { + Self::read_file(path).and_then(|contents| Ok(self.compile_with_scope(scope, &contents)?)) + } + /// Evaluate a script file. + /// + /// Not available under `no_std` or `WASM`. + /// + /// # Example + /// + /// ```no_run + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// // Notice that a PathBuf is required which can easily be constructed from a string. + /// let result = engine.eval_file::("script.rhai".into())?; + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn eval_file( + &self, + path: std::path::PathBuf, + ) -> Result> { + Self::read_file(path).and_then(|contents| self.eval::(&contents)) + } + /// Evaluate a script file with own scope. + /// + /// Not available under `no_std` or `WASM`. + /// + /// ## Constants Propagation + /// + /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated + /// throughout the script _including_ functions. This allows functions to be optimized based on + /// dynamic global constants. + /// + /// # Example + /// + /// ```no_run + /// # fn main() -> Result<(), Box> { + /// use rhai::{Engine, Scope}; + /// + /// let engine = Engine::new(); + /// + /// // Create initialized scope + /// let mut scope = Scope::new(); + /// scope.push("x", 42_i64); + /// + /// // Notice that a PathBuf is required which can easily be constructed from a string. + /// let result = engine.eval_file_with_scope::(&mut scope, "script.rhai".into())?; + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn eval_file_with_scope( + &self, + scope: &mut Scope, + path: std::path::PathBuf, + ) -> Result> { + Self::read_file(path).and_then(|contents| self.eval_with_scope::(scope, &contents)) + } + /// Evaluate a file, returning any error (if any). + /// + /// Not available under `no_std` or `WASM`. + #[cfg(not(feature = "no_std"))] + #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] + #[inline] + pub fn run_file(&self, path: std::path::PathBuf) -> Result<(), Box> { + Self::read_file(path).and_then(|contents| self.run(&contents)) + } + /// Evaluate a file with own scope, returning any error (if any). + /// + /// Not available under `no_std` or `WASM`. + /// + /// ## Constants Propagation + /// + /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated + /// throughout the script _including_ functions. This allows functions to be optimized based on + /// dynamic global constants. + #[cfg(not(feature = "no_std"))] + #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] + #[inline] + pub fn run_file_with_scope( + &self, + scope: &mut Scope, + path: std::path::PathBuf, + ) -> Result<(), Box> { + Self::read_file(path).and_then(|contents| self.run_with_scope(scope, &contents)) + } + /// Evaluate a script, returning any error (if any). + #[inline(always)] + pub fn run(&self, script: &str) -> Result<(), Box> { + self.run_with_scope(&mut Scope::new(), script) + } +} diff --git a/src/api/limits.rs b/src/api/limits.rs new file mode 100644 index 00000000..0e9990e6 --- /dev/null +++ b/src/api/limits.rs @@ -0,0 +1,174 @@ +//! Settings for [`Engine`]'s limitations. +#![cfg(not(feature = "unchecked"))] + +use crate::Engine; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +use std::num::{NonZeroU64, NonZeroUsize}; + +impl Engine { + /// Set the maximum levels of function calls allowed for a script in order to avoid + /// infinite recursion and stack overflows. + /// + /// Not available under `unchecked` or `no_function`. + #[cfg(not(feature = "no_function"))] + #[inline(always)] + pub fn set_max_call_levels(&mut self, levels: usize) -> &mut Self { + self.limits.max_call_stack_depth = levels; + self + } + /// The maximum levels of function calls allowed for a script. + /// + /// Not available under `unchecked` or `no_function`. + #[cfg(not(feature = "no_function"))] + #[inline(always)] + #[must_use] + pub const fn max_call_levels(&self) -> usize { + self.limits.max_call_stack_depth + } + /// Set the maximum number of operations allowed for a script to run to avoid + /// consuming too much resources (0 for unlimited). + /// + /// Not available under `unchecked`. + #[inline(always)] + pub fn set_max_operations(&mut self, operations: u64) -> &mut Self { + self.limits.max_operations = NonZeroU64::new(operations); + self + } + /// The maximum number of operations allowed for a script to run (0 for unlimited). + /// + /// Not available under `unchecked`. + #[inline] + #[must_use] + pub const fn max_operations(&self) -> u64 { + if let Some(n) = self.limits.max_operations { + n.get() + } else { + 0 + } + } + /// Set the maximum number of imported [modules][crate::Module] allowed for a script. + /// + /// Not available under `unchecked` or `no_module`. + #[cfg(not(feature = "no_module"))] + #[inline(always)] + pub fn set_max_modules(&mut self, modules: usize) -> &mut Self { + self.limits.max_modules = modules; + self + } + /// The maximum number of imported [modules][crate::Module] allowed for a script. + /// + /// Not available under `unchecked` or `no_module`. + #[cfg(not(feature = "no_module"))] + #[inline(always)] + #[must_use] + pub const fn max_modules(&self) -> usize { + self.limits.max_modules + } + /// Set the depth limits for expressions (0 for unlimited). + /// + /// Not available under `unchecked`. + #[inline(always)] + pub fn set_max_expr_depths( + &mut self, + max_expr_depth: usize, + #[cfg(not(feature = "no_function"))] max_function_expr_depth: usize, + ) -> &mut Self { + self.limits.max_expr_depth = NonZeroUsize::new(max_expr_depth); + #[cfg(not(feature = "no_function"))] + { + self.limits.max_function_expr_depth = NonZeroUsize::new(max_function_expr_depth); + } + self + } + /// The depth limit for expressions (0 for unlimited). + /// + /// Not available under `unchecked`. + #[inline] + #[must_use] + pub const fn max_expr_depth(&self) -> usize { + if let Some(n) = self.limits.max_expr_depth { + n.get() + } else { + 0 + } + } + /// The depth limit for expressions in functions (0 for unlimited). + /// + /// Not available under `unchecked` or `no_function`. + #[cfg(not(feature = "no_function"))] + #[inline] + #[must_use] + pub const fn max_function_expr_depth(&self) -> usize { + if let Some(n) = self.limits.max_function_expr_depth { + n.get() + } else { + 0 + } + } + /// Set the maximum length of [strings][crate::ImmutableString] (0 for unlimited). + /// + /// Not available under `unchecked`. + #[inline(always)] + pub fn set_max_string_size(&mut self, max_size: usize) -> &mut Self { + self.limits.max_string_size = NonZeroUsize::new(max_size); + self + } + /// The maximum length of [strings][crate::ImmutableString] (0 for unlimited). + /// + /// Not available under `unchecked`. + #[inline] + #[must_use] + pub const fn max_string_size(&self) -> usize { + if let Some(n) = self.limits.max_string_size { + n.get() + } else { + 0 + } + } + /// Set the maximum length of [arrays][crate::Array] (0 for unlimited). + /// + /// Not available under `unchecked` or `no_index`. + #[cfg(not(feature = "no_index"))] + #[inline(always)] + pub fn set_max_array_size(&mut self, max_size: usize) -> &mut Self { + self.limits.max_array_size = NonZeroUsize::new(max_size); + self + } + /// The maximum length of [arrays][crate::Array] (0 for unlimited). + /// + /// Not available under `unchecked` or `no_index`. + #[cfg(not(feature = "no_index"))] + #[inline] + #[must_use] + pub const fn max_array_size(&self) -> usize { + if let Some(n) = self.limits.max_array_size { + n.get() + } else { + 0 + } + } + /// Set the maximum size of [object maps][crate::Map] (0 for unlimited). + /// + /// Not available under `unchecked` or `no_object`. + #[cfg(not(feature = "no_object"))] + #[inline(always)] + pub fn set_max_map_size(&mut self, max_size: usize) -> &mut Self { + self.limits.max_map_size = NonZeroUsize::new(max_size); + self + } + /// The maximum size of [object maps][crate::Map] (0 for unlimited). + /// + /// Not available under `unchecked` or `no_object`. + #[cfg(not(feature = "no_object"))] + #[inline] + #[must_use] + pub const fn max_map_size(&self) -> usize { + if let Some(n) = self.limits.max_map_size { + n.get() + } else { + 0 + } + } +} diff --git a/src/api/mod.rs b/src/api/mod.rs index 6b560f94..3d8dede0 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,5 +1,215 @@ //! Module defining the public API of the Rhai engine. +pub mod call_fn; +pub mod compile; pub mod deprecated; -pub mod public; -pub mod settings; +pub mod eval; +pub mod events; +#[cfg(not(feature = "no_std"))] +#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] +pub mod files; +#[cfg(not(feature = "unchecked"))] +pub mod limits; +pub mod register; +pub mod run; + +use crate::engine::Precedence; +use crate::tokenizer::Token; +use crate::{Engine, Identifier}; + +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +/// Script optimization API. +#[cfg(not(feature = "no_optimize"))] +impl Engine { + /// Control whether and how the [`Engine`] will optimize an [`AST`][crate::AST] after compilation. + /// + /// Not available under `no_optimize`. + #[inline(always)] + pub fn set_optimization_level( + &mut self, + optimization_level: crate::OptimizationLevel, + ) -> &mut Self { + self.optimization_level = optimization_level; + self + } + /// The current optimization level. + /// It controls whether and how the [`Engine`] will optimize an [`AST`][crate::AST] after compilation. + /// + /// Not available under `no_optimize`. + #[inline(always)] + #[must_use] + pub const fn optimization_level(&self) -> crate::OptimizationLevel { + self.optimization_level + } + /// Optimize the [`AST`] with constants defined in an external Scope. + /// An optimized copy of the [`AST`] is returned while the original [`AST`] is consumed. + /// + /// Not available under `no_optimize`. + /// + /// Although optimization is performed by default during compilation, sometimes it is necessary to + /// _re_-optimize an [`AST`]. For example, when working with constants that are passed in via an + /// external scope, it will be more efficient to optimize the [`AST`] once again to take advantage + /// of the new constants. + /// + /// With this method, it is no longer necessary to recompile a large script. + /// The script [`AST`] can be compiled just once. Before evaluation, + /// constants are passed into the [`Engine`] via an external scope + /// (i.e. with [`Scope::push_constant`]). + /// Then, the [`AST`] is cloned and the copy re-optimized before running. + #[inline] + #[must_use] + pub fn optimize_ast( + &self, + scope: &crate::Scope, + ast: crate::AST, + optimization_level: crate::OptimizationLevel, + ) -> crate::AST { + let mut ast = ast; + + #[cfg(not(feature = "no_function"))] + let lib = ast + .lib() + .iter_fn() + .filter(|f| f.func.is_script()) + .map(|f| { + f.func + .get_script_fn_def() + .expect("scripted function") + .clone() + }) + .collect(); + + #[cfg(feature = "no_function")] + let lib = crate::StaticVec::new(); + + let statements = std::mem::take(ast.statements_mut()); + + crate::optimizer::optimize_into_ast(self, scope, statements, lib, optimization_level) + } +} + +impl Engine { + /// Set the module resolution service used by the [`Engine`]. + /// + /// Not available under `no_module`. + #[cfg(not(feature = "no_module"))] + #[inline(always)] + pub fn set_module_resolver( + &mut self, + resolver: impl crate::ModuleResolver + 'static, + ) -> &mut Self { + self.module_resolver = Some(Box::new(resolver)); + self + } + /// Disable a particular keyword or operator in the language. + /// + /// # Examples + /// + /// The following will raise an error during parsing because the `if` keyword is disabled + /// and is recognized as a reserved symbol! + /// + /// ```rust,should_panic + /// # fn main() -> Result<(), rhai::ParseError> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// engine.disable_symbol("if"); // disable the 'if' keyword + /// + /// engine.compile("let x = if true { 42 } else { 0 };")?; + /// // ^ 'if' is rejected as a reserved symbol + /// # Ok(()) + /// # } + /// ``` + /// + /// The following will raise an error during parsing because the `+=` operator is disabled. + /// + /// ```rust,should_panic + /// # fn main() -> Result<(), rhai::ParseError> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// engine.disable_symbol("+="); // disable the '+=' operator + /// + /// engine.compile("let x = 42; x += 1;")?; + /// // ^ unknown operator + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn disable_symbol(&mut self, symbol: impl Into) -> &mut Self { + self.disabled_symbols.insert(symbol.into()); + self + } + /// Register a custom operator with a precedence into the language. + /// + /// The operator must be a valid identifier (i.e. it cannot be a symbol). + /// + /// The precedence cannot be zero. + /// + /// # Example + /// + /// ```rust + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// // Register a custom operator called 'foo' and give it + /// // a precedence of 160 (i.e. between +|- and *|/). + /// engine.register_custom_operator("foo", 160).expect("should succeed"); + /// + /// // Register a binary function named 'foo' + /// engine.register_fn("foo", |x: i64, y: i64| (x * y) - (x + y)); + /// + /// assert_eq!( + /// engine.eval_expression::("1 + 2 * 3 foo 4 - 5 / 6")?, + /// 15 + /// ); + /// # Ok(()) + /// # } + /// ``` + pub fn register_custom_operator( + &mut self, + keyword: impl AsRef + Into, + precedence: u8, + ) -> Result<&mut Self, String> { + let precedence = Precedence::new(precedence); + + if precedence.is_none() { + return Err("precedence cannot be zero".into()); + } + + match Token::lookup_from_syntax(keyword.as_ref()) { + // Standard identifiers, reserved keywords and custom keywords are OK + None | Some(Token::Reserved(_)) | Some(Token::Custom(_)) => (), + // Active standard keywords cannot be made custom + // Disabled keywords are OK + Some(token) if token.is_standard_keyword() => { + if !self.disabled_symbols.contains(&*token.syntax()) { + return Err(format!("'{}' is a reserved keyword", keyword.as_ref())); + } + } + // Active standard symbols cannot be made custom + Some(token) if token.is_standard_symbol() => { + if !self.disabled_symbols.contains(&*token.syntax()) { + return Err(format!("'{}' is a reserved operator", keyword.as_ref())); + } + } + // Active standard symbols cannot be made custom + Some(token) if !self.disabled_symbols.contains(&*token.syntax()) => { + return Err(format!("'{}' is a reserved symbol", keyword.as_ref())) + } + // Disabled symbols are OK + Some(_) => (), + } + + // Add to custom keywords + self.custom_keywords.insert(keyword.into(), precedence); + + Ok(self) + } +} diff --git a/src/api/public.rs b/src/api/public.rs deleted file mode 100644 index 08a695b4..00000000 --- a/src/api/public.rs +++ /dev/null @@ -1,2365 +0,0 @@ -//! Module that defines the public API of [`Engine`]. - -use crate::engine::{EvalContext, EvalState, Imports}; -use crate::func::{FnCallArgs, RegisterNativeFunction, SendSync}; -use crate::parser::ParseState; -use crate::types::dynamic::Variant; -use crate::{ - Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, Identifier, Module, NativeCallContext, - ParseError, Position, RhaiResult, Scope, Shared, AST, -}; -use std::any::{type_name, TypeId}; -#[cfg(feature = "no_std")] -use std::prelude::v1::*; - -#[cfg(not(feature = "no_index"))] -use crate::Array; - -#[cfg(not(feature = "no_object"))] -use crate::Map; - -impl Engine { - /// Get the global namespace module (which is the last module in `global_modules`). - #[inline(always)] - #[allow(dead_code)] - pub(crate) fn global_namespace(&self) -> &Module { - self.global_modules.first().expect("not empty") - } - /// Get a mutable reference to the global namespace module - /// (which is the first module in `global_modules`). - #[inline(always)] - pub(crate) fn global_namespace_mut(&mut self) -> &mut Module { - Shared::get_mut(self.global_modules.first_mut().expect("not empty")).expect("not shared") - } - /// Register a custom function with the [`Engine`]. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// // Normal function - /// fn add(x: i64, y: i64) -> i64 { - /// x + y - /// } - /// - /// let mut engine = Engine::new(); - /// - /// engine.register_fn("add", add); - /// - /// assert_eq!(engine.eval::("add(40, 2)")?, 42); - /// - /// // You can also register a closure. - /// engine.register_fn("sub", |x: i64, y: i64| x - y ); - /// - /// assert_eq!(engine.eval::("sub(44, 2)")?, 42); - /// # Ok(()) - /// # } - /// ``` - #[inline] - pub fn register_fn(&mut self, name: N, func: F) -> &mut Self - where - N: AsRef + Into, - F: RegisterNativeFunction, - { - let param_types = F::param_types(); - - #[cfg(feature = "metadata")] - let mut param_type_names: crate::StaticVec<_> = F::param_names() - .iter() - .map(|ty| format!("_: {}", self.map_type_name(ty))) - .collect(); - - #[cfg(feature = "metadata")] - if F::return_type() != TypeId::of::<()>() { - param_type_names.push(self.map_type_name(F::return_type_name()).into()); - } - - #[cfg(feature = "metadata")] - let param_type_names: Option> = - Some(param_type_names.iter().map(|ty| ty.as_str()).collect()); - - #[cfg(not(feature = "metadata"))] - let param_type_names: Option<[&str; 0]> = None; - - self.global_namespace_mut().set_fn( - name, - FnNamespace::Global, - FnAccess::Public, - param_type_names.as_ref().map(|v| v.as_ref()), - ¶m_types, - func.into_callable_function(), - ); - self - } - /// Register a custom fallible function with the [`Engine`]. - /// - /// # Example - /// - /// ``` - /// use rhai::{Engine, EvalAltResult}; - /// - /// // Normal function - /// fn div(x: i64, y: i64) -> Result> { - /// if y == 0 { - /// // '.into()' automatically converts to 'Box' - /// Err("division by zero!".into()) - /// } else { - /// Ok(x / y) - /// } - /// } - /// - /// let mut engine = Engine::new(); - /// - /// engine.register_result_fn("div", div); - /// - /// engine.eval::("div(42, 0)") - /// .expect_err("expecting division by zero error!"); - /// ``` - #[inline] - pub fn register_result_fn(&mut self, name: N, func: F) -> &mut Self - where - N: AsRef + Into, - F: RegisterNativeFunction>>, - { - let param_types = F::param_types(); - - #[cfg(feature = "metadata")] - let param_type_names: crate::StaticVec<_> = F::param_names() - .iter() - .map(|ty| format!("_: {}", self.map_type_name(ty))) - .chain(std::iter::once( - self.map_type_name(F::return_type_name()).into(), - )) - .collect(); - - #[cfg(feature = "metadata")] - let param_type_names: Option> = - Some(param_type_names.iter().map(|ty| ty.as_str()).collect()); - - #[cfg(not(feature = "metadata"))] - let param_type_names: Option<[&str; 0]> = None; - - self.global_namespace_mut().set_fn( - name, - FnNamespace::Global, - FnAccess::Public, - param_type_names.as_ref().map(|v| v.as_ref()), - ¶m_types, - func.into_callable_function(), - ); - self - } - /// Register a function of the [`Engine`]. - /// - /// # WARNING - Low Level API - /// - /// This function is very low level. It takes a list of [`TypeId`][std::any::TypeId]'s indicating the actual types of the parameters. - /// - /// Arguments are simply passed in as a mutable array of [`&mut Dynamic`][Dynamic], - /// The arguments are guaranteed to be of the correct types matching the [`TypeId`][std::any::TypeId]'s. - /// - /// To access a primary argument value (i.e. cloning is cheap), use: `args[n].as_xxx().unwrap()` - /// - /// To access an argument value and avoid cloning, use `std::mem::take(args[n]).cast::()`. - /// Notice that this will _consume_ the argument, replacing it with `()`. - /// - /// To access the first mutable parameter, use `args.get_mut(0).unwrap()` - #[deprecated = "this function is volatile and may change"] - #[inline(always)] - pub fn register_raw_fn( - &mut self, - name: N, - arg_types: &[TypeId], - func: impl Fn(NativeCallContext, &mut FnCallArgs) -> Result> - + SendSync - + 'static, - ) -> &mut Self - where - N: AsRef + Into, - T: Variant + Clone, - { - self.global_namespace_mut().set_raw_fn( - name, - FnNamespace::Global, - FnAccess::Public, - arg_types, - func, - ); - self - } - /// Register a custom type for use with the [`Engine`]. - /// The type must implement [`Clone`]. - /// - /// # Example - /// - /// ``` - /// #[derive(Debug, Clone, Eq, PartialEq)] - /// struct TestStruct { - /// field: i64 - /// } - /// - /// impl TestStruct { - /// fn new() -> Self { Self { field: 1 } } - /// fn update(&mut self, offset: i64) { self.field += offset; } - /// } - /// - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let mut engine = Engine::new(); - /// - /// // Register API for the custom type. - /// engine - /// .register_type::() - /// .register_fn("new_ts", TestStruct::new) - /// // Use `register_fn` to register methods on the type. - /// .register_fn("update", TestStruct::update); - /// - /// # #[cfg(not(feature = "no_object"))] - /// assert_eq!( - /// engine.eval::("let x = new_ts(); x.update(41); x")?, - /// TestStruct { field: 42 } - /// ); - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn register_type(&mut self) -> &mut Self { - self.register_type_with_name::(type_name::()) - } - /// Register a custom type for use with the [`Engine`], with a pretty-print name - /// for the `type_of` function. The type must implement [`Clone`]. - /// - /// # Example - /// - /// ``` - /// #[derive(Clone)] - /// struct TestStruct { - /// field: i64 - /// } - /// - /// impl TestStruct { - /// fn new() -> Self { Self { field: 1 } } - /// } - /// - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let mut engine = Engine::new(); - /// - /// // Register API for the custom type. - /// engine - /// .register_type::() - /// .register_fn("new_ts", TestStruct::new); - /// - /// assert_eq!( - /// engine.eval::("let x = new_ts(); type_of(x)")?, - /// "rust_out::TestStruct" - /// ); - /// - /// // Re-register the custom type with a name. - /// engine.register_type_with_name::("Hello"); - /// - /// assert_eq!( - /// engine.eval::("let x = new_ts(); type_of(x)")?, - /// "Hello" - /// ); - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn register_type_with_name(&mut self, name: &str) -> &mut Self { - // Add the pretty-print type name into the map - self.type_names - .insert(type_name::().into(), Box::new(name.into())); - self - } - /// Register an type iterator for an iterable type with the [`Engine`]. - /// This is an advanced feature. - #[inline(always)] - pub fn register_iterator(&mut self) -> &mut Self - where - T: Variant + Clone + IntoIterator, - ::Item: Variant + Clone, - { - self.global_namespace_mut().set_iterable::(); - self - } - /// Register a getter function for a member of a registered type with the [`Engine`]. - /// - /// The function signature must start with `&mut self` and not `&self`. - /// - /// Not available under `no_object`. - /// - /// # Example - /// - /// ``` - /// #[derive(Clone)] - /// struct TestStruct { - /// field: i64 - /// } - /// - /// impl TestStruct { - /// fn new() -> Self { Self { field: 1 } } - /// // Even a getter must start with `&mut self` and not `&self`. - /// fn get_field(&mut self) -> i64 { self.field } - /// } - /// - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let mut engine = Engine::new(); - /// - /// // Register API for the custom type. - /// engine - /// .register_type::() - /// .register_fn("new_ts", TestStruct::new) - /// // Register a getter on a property (notice it doesn't have to be the same name). - /// .register_get("xyz", TestStruct::get_field); - /// - /// assert_eq!(engine.eval::("let a = new_ts(); a.xyz")?, 1); - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_object"))] - #[inline(always)] - pub fn register_get( - &mut self, - name: &str, - get_fn: impl Fn(&mut T) -> V + SendSync + 'static, - ) -> &mut Self { - self.register_fn(&crate::engine::make_getter(name), get_fn) - } - /// Register a getter function for a member of a registered type with the [`Engine`]. - /// - /// The function signature must start with `&mut self` and not `&self`. - /// - /// Not available under `no_object`. - /// - /// # Example - /// - /// ``` - /// use rhai::{Engine, Dynamic, EvalAltResult}; - /// - /// #[derive(Clone)] - /// struct TestStruct { - /// field: i64 - /// } - /// - /// impl TestStruct { - /// fn new() -> Self { Self { field: 1 } } - /// // Even a getter must start with `&mut self` and not `&self`. - /// fn get_field(&mut self) -> Result> { - /// Ok(self.field) - /// } - /// } - /// - /// # fn main() -> Result<(), Box> { - /// let mut engine = Engine::new(); - /// - /// // Register API for the custom type. - /// engine - /// .register_type::() - /// .register_fn("new_ts", TestStruct::new) - /// // Register a getter on a property (notice it doesn't have to be the same name). - /// .register_get_result("xyz", TestStruct::get_field); - /// - /// assert_eq!(engine.eval::("let a = new_ts(); a.xyz")?, 1); - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_object"))] - #[inline(always)] - pub fn register_get_result( - &mut self, - name: &str, - get_fn: impl Fn(&mut T) -> Result> + SendSync + 'static, - ) -> &mut Self { - self.register_result_fn(&crate::engine::make_getter(name), get_fn) - } - /// Register a setter function for a member of a registered type with the [`Engine`]. - /// - /// Not available under `no_object`. - /// - /// # Example - /// - /// ``` - /// #[derive(Debug, Clone, Eq, PartialEq)] - /// struct TestStruct { - /// field: i64 - /// } - /// - /// impl TestStruct { - /// fn new() -> Self { Self { field: 1 } } - /// fn set_field(&mut self, new_val: i64) { self.field = new_val; } - /// } - /// - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let mut engine = Engine::new(); - /// - /// // Register API for the custom type. - /// engine - /// .register_type::() - /// .register_fn("new_ts", TestStruct::new) - /// // Register a setter on a property (notice it doesn't have to be the same name) - /// .register_set("xyz", TestStruct::set_field); - /// - /// // Notice that, with a getter, there is no way to get the property value - /// assert_eq!( - /// engine.eval::("let a = new_ts(); a.xyz = 42; a")?, - /// TestStruct { field: 42 } - /// ); - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_object"))] - #[inline(always)] - pub fn register_set( - &mut self, - name: &str, - set_fn: impl Fn(&mut T, V) + SendSync + 'static, - ) -> &mut Self { - self.register_fn(&crate::engine::make_setter(name), set_fn) - } - /// Register a setter function for a member of a registered type with the [`Engine`]. - /// - /// Not available under `no_object`. - /// - /// # Example - /// - /// ``` - /// use rhai::{Engine, Dynamic, EvalAltResult}; - /// - /// #[derive(Debug, Clone, Eq, PartialEq)] - /// struct TestStruct { - /// field: i64 - /// } - /// - /// impl TestStruct { - /// fn new() -> Self { Self { field: 1 } } - /// fn set_field(&mut self, new_val: i64) -> Result<(), Box> { - /// self.field = new_val; - /// Ok(()) - /// } - /// } - /// - /// # fn main() -> Result<(), Box> { - /// let mut engine = Engine::new(); - /// - /// // Register API for the custom type. - /// engine - /// .register_type::() - /// .register_fn("new_ts", TestStruct::new) - /// // Register a setter on a property (notice it doesn't have to be the same name) - /// .register_set_result("xyz", TestStruct::set_field); - /// - /// // Notice that, with a getter, there is no way to get the property value - /// assert_eq!( - /// engine.eval::("let a = new_ts(); a.xyz = 42; a")?, - /// TestStruct { field: 42 } - /// ); - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_object"))] - #[inline(always)] - pub fn register_set_result( - &mut self, - name: &str, - set_fn: impl Fn(&mut T, V) -> Result<(), Box> + SendSync + 'static, - ) -> &mut Self { - self.register_result_fn(&crate::engine::make_setter(name), set_fn) - } - /// Short-hand for registering both getter and setter functions - /// of a registered type with the [`Engine`]. - /// - /// All function signatures must start with `&mut self` and not `&self`. - /// - /// Not available under `no_object`. - /// - /// # Example - /// - /// ``` - /// #[derive(Clone)] - /// struct TestStruct { - /// field: i64 - /// } - /// - /// impl TestStruct { - /// fn new() -> Self { Self { field: 1 } } - /// // Even a getter must start with `&mut self` and not `&self`. - /// fn get_field(&mut self) -> i64 { self.field } - /// fn set_field(&mut self, new_val: i64) { self.field = new_val; } - /// } - /// - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let mut engine = Engine::new(); - /// - /// // Register API for the custom type. - /// engine - /// .register_type::() - /// .register_fn("new_ts", TestStruct::new) - /// // Register both a getter and a setter on a property - /// // (notice it doesn't have to be the same name) - /// .register_get_set("xyz", TestStruct::get_field, TestStruct::set_field); - /// - /// assert_eq!(engine.eval::("let a = new_ts(); a.xyz = 42; a.xyz")?, 42); - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_object"))] - #[inline(always)] - pub fn register_get_set( - &mut self, - name: &str, - get_fn: impl Fn(&mut T) -> V + SendSync + 'static, - set_fn: impl Fn(&mut T, V) + SendSync + 'static, - ) -> &mut Self { - self.register_get(name, get_fn).register_set(name, set_fn) - } - /// Register an index getter for a custom type with the [`Engine`]. - /// - /// The function signature must start with `&mut self` and not `&self`. - /// - /// Not available under both `no_index` and `no_object`. - /// - /// # Panics - /// - /// Panics if the type is [`Array`], [`Map`], [`String`], - /// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT]. - /// Indexers for arrays, object maps, strings and integers cannot be registered. - /// - /// # Example - /// - /// ``` - /// #[derive(Clone)] - /// struct TestStruct { - /// fields: Vec - /// } - /// - /// impl TestStruct { - /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } - /// // Even a getter must start with `&mut self` and not `&self`. - /// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] } - /// } - /// - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let mut engine = Engine::new(); - /// - /// // Register API for the custom type. - /// # #[cfg(not(feature = "no_object"))] - /// engine.register_type::(); - /// - /// engine - /// .register_fn("new_ts", TestStruct::new) - /// // Register an indexer. - /// .register_indexer_get(TestStruct::get_field); - /// - /// # #[cfg(not(feature = "no_index"))] - /// assert_eq!(engine.eval::("let a = new_ts(); a[2]")?, 3); - /// # Ok(()) - /// # } - /// ``` - #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - #[inline] - pub fn register_indexer_get( - &mut self, - get_fn: impl Fn(&mut T, X) -> V + SendSync + 'static, - ) -> &mut Self { - #[cfg(not(feature = "no_index"))] - if TypeId::of::() == TypeId::of::() { - panic!("Cannot register indexer for arrays."); - } - #[cfg(not(feature = "no_object"))] - if TypeId::of::() == TypeId::of::() { - panic!("Cannot register indexer for object maps."); - } - if TypeId::of::() == TypeId::of::() - || TypeId::of::() == TypeId::of::<&str>() - || TypeId::of::() == TypeId::of::() - { - panic!("Cannot register indexer for strings."); - } - if TypeId::of::() == TypeId::of::() { - panic!("Cannot register indexer for integers."); - } - - self.register_fn(crate::engine::FN_IDX_GET, get_fn) - } - /// Register an index getter for a custom type with the [`Engine`]. - /// - /// The function signature must start with `&mut self` and not `&self`. - /// - /// Not available under both `no_index` and `no_object`. - /// - /// # Panics - /// - /// Panics if the type is [`Array`], [`Map`], [`String`], - /// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT]. - /// Indexers for arrays, object maps, strings and integers cannot be registered. - /// - /// # Example - /// - /// ``` - /// use rhai::{Engine, Dynamic, EvalAltResult}; - /// - /// #[derive(Clone)] - /// struct TestStruct { - /// fields: Vec - /// } - /// - /// impl TestStruct { - /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } - /// // Even a getter must start with `&mut self` and not `&self`. - /// fn get_field(&mut self, index: i64) -> Result> { - /// Ok(self.fields[index as usize]) - /// } - /// } - /// - /// # fn main() -> Result<(), Box> { - /// let mut engine = Engine::new(); - /// - /// // Register API for the custom type. - /// # #[cfg(not(feature = "no_object"))] - /// engine.register_type::(); - /// - /// engine - /// .register_fn("new_ts", TestStruct::new) - /// // Register an indexer. - /// .register_indexer_get_result(TestStruct::get_field); - /// - /// # #[cfg(not(feature = "no_index"))] - /// assert_eq!(engine.eval::("let a = new_ts(); a[2]")?, 3); - /// # Ok(()) - /// # } - /// ``` - #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - #[inline] - pub fn register_indexer_get_result< - T: Variant + Clone, - X: Variant + Clone, - V: Variant + Clone, - >( - &mut self, - get_fn: impl Fn(&mut T, X) -> Result> + SendSync + 'static, - ) -> &mut Self { - #[cfg(not(feature = "no_index"))] - if TypeId::of::() == TypeId::of::() { - panic!("Cannot register indexer for arrays."); - } - #[cfg(not(feature = "no_object"))] - if TypeId::of::() == TypeId::of::() { - panic!("Cannot register indexer for object maps."); - } - if TypeId::of::() == TypeId::of::() - || TypeId::of::() == TypeId::of::<&str>() - || TypeId::of::() == TypeId::of::() - { - panic!("Cannot register indexer for strings."); - } - if TypeId::of::() == TypeId::of::() { - panic!("Cannot register indexer for integers."); - } - - self.register_result_fn(crate::engine::FN_IDX_GET, get_fn) - } - /// Register an index setter for a custom type with the [`Engine`]. - /// - /// Not available under both `no_index` and `no_object`. - /// - /// # Panics - /// - /// Panics if the type is [`Array`], [`Map`], [`String`], - /// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT]. - /// Indexers for arrays, object maps, strings and integers cannot be registered. - /// - /// # Example - /// - /// ``` - /// #[derive(Clone)] - /// struct TestStruct { - /// fields: Vec - /// } - /// - /// impl TestStruct { - /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } - /// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; } - /// } - /// - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let mut engine = Engine::new(); - /// - /// // Register API for the custom type. - /// # #[cfg(not(feature = "no_object"))] - /// engine.register_type::(); - /// - /// engine - /// .register_fn("new_ts", TestStruct::new) - /// // Register an indexer. - /// .register_indexer_set(TestStruct::set_field); - /// - /// # #[cfg(not(feature = "no_index"))] - /// assert_eq!( - /// engine.eval::("let a = new_ts(); a[2] = 42; a")?.fields[2], - /// 42 - /// ); - /// # Ok(()) - /// # } - /// ``` - #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - #[inline] - pub fn register_indexer_set( - &mut self, - set_fn: impl Fn(&mut T, X, V) + SendSync + 'static, - ) -> &mut Self { - #[cfg(not(feature = "no_index"))] - if TypeId::of::() == TypeId::of::() { - panic!("Cannot register indexer for arrays."); - } - #[cfg(not(feature = "no_object"))] - if TypeId::of::() == TypeId::of::() { - panic!("Cannot register indexer for object maps."); - } - if TypeId::of::() == TypeId::of::() - || TypeId::of::() == TypeId::of::<&str>() - || TypeId::of::() == TypeId::of::() - { - panic!("Cannot register indexer for strings."); - } - if TypeId::of::() == TypeId::of::() { - panic!("Cannot register indexer for integers."); - } - - self.register_fn(crate::engine::FN_IDX_SET, set_fn) - } - /// Register an index setter for a custom type with the [`Engine`]. - /// - /// Not available under both `no_index` and `no_object`. - /// - /// # Panics - /// - /// Panics if the type is [`Array`], [`Map`], [`String`], - /// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT]. - /// Indexers for arrays, object maps, strings and integers cannot be registered. - /// - /// # Example - /// - /// ``` - /// use rhai::{Engine, Dynamic, EvalAltResult}; - /// - /// #[derive(Clone)] - /// struct TestStruct { - /// fields: Vec - /// } - /// - /// impl TestStruct { - /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } - /// fn set_field(&mut self, index: i64, value: i64) -> Result<(), Box> { - /// self.fields[index as usize] = value; - /// Ok(()) - /// } - /// } - /// - /// # fn main() -> Result<(), Box> { - /// let mut engine = Engine::new(); - /// - /// // Register API for the custom type. - /// # #[cfg(not(feature = "no_object"))] - /// engine.register_type::(); - /// - /// engine - /// .register_fn("new_ts", TestStruct::new) - /// // Register an indexer. - /// .register_indexer_set_result(TestStruct::set_field); - /// - /// # #[cfg(not(feature = "no_index"))] - /// assert_eq!( - /// engine.eval::("let a = new_ts(); a[2] = 42; a")?.fields[2], - /// 42 - /// ); - /// # Ok(()) - /// # } - /// ``` - #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - #[inline] - pub fn register_indexer_set_result< - T: Variant + Clone, - X: Variant + Clone, - V: Variant + Clone, - >( - &mut self, - set_fn: impl Fn(&mut T, X, V) -> Result<(), Box> + SendSync + 'static, - ) -> &mut Self { - #[cfg(not(feature = "no_index"))] - if TypeId::of::() == TypeId::of::() { - panic!("Cannot register indexer for arrays."); - } - #[cfg(not(feature = "no_object"))] - if TypeId::of::() == TypeId::of::() { - panic!("Cannot register indexer for object maps."); - } - if TypeId::of::() == TypeId::of::() - || TypeId::of::() == TypeId::of::<&str>() - || TypeId::of::() == TypeId::of::() - { - panic!("Cannot register indexer for strings."); - } - if TypeId::of::() == TypeId::of::() { - panic!("Cannot register indexer for integers."); - } - - self.register_result_fn(crate::engine::FN_IDX_SET, set_fn) - } - /// Short-hand for registering both index getter and setter functions for a custom type with the [`Engine`]. - /// - /// Not available under both `no_index` and `no_object`. - /// - /// # Panics - /// - /// Panics if the type is [`Array`], [`Map`], [`String`], - /// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT]. - /// Indexers for arrays, object maps, strings and integers cannot be registered. - /// - /// # Example - /// - /// ``` - /// #[derive(Clone)] - /// struct TestStruct { - /// fields: Vec - /// } - /// - /// impl TestStruct { - /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } - /// // Even a getter must start with `&mut self` and not `&self`. - /// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] } - /// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; } - /// } - /// - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let mut engine = Engine::new(); - /// - /// // Register API for the custom type. - /// # #[cfg(not(feature = "no_object"))] - /// engine.register_type::(); - /// - /// engine - /// .register_fn("new_ts", TestStruct::new) - /// // Register an indexer. - /// .register_indexer_get_set(TestStruct::get_field, TestStruct::set_field); - /// - /// # #[cfg(not(feature = "no_index"))] - /// assert_eq!(engine.eval::("let a = new_ts(); a[2] = 42; a[2]")?, 42); - /// # Ok(()) - /// # } - /// ``` - #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - #[inline(always)] - pub fn register_indexer_get_set( - &mut self, - get_fn: impl Fn(&mut T, X) -> V + SendSync + 'static, - set_fn: impl Fn(&mut T, X, V) + SendSync + 'static, - ) -> &mut Self { - self.register_indexer_get(get_fn) - .register_indexer_set(set_fn) - } - /// Register a shared [`Module`] into the global namespace of [`Engine`]. - /// - /// All functions and type iterators are automatically available to scripts without namespace - /// qualifications. - /// - /// Sub-modules and variables are **ignored**. - /// - /// When searching for functions, modules loaded later are preferred. In other words, loaded - /// modules are searched in reverse order. - #[inline(always)] - pub fn register_global_module(&mut self, module: Shared) -> &mut Self { - // Insert the module into the front. - // The first module is always the global namespace. - self.global_modules.insert(1, module); - self - } - /// Register a shared [`Module`] as a static module namespace with the [`Engine`]. - /// - /// Functions marked [`FnNamespace::Global`] and type iterators are exposed to scripts without - /// namespace qualifications. - /// - /// Not available under `no_module`. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::{Engine, Shared, Module}; - /// - /// let mut engine = Engine::new(); - /// - /// // Create the module - /// let mut module = Module::new(); - /// module.set_native_fn("calc", |x: i64| Ok(x + 1)); - /// - /// let module: Shared = module.into(); - /// - /// engine - /// // Register the module as a fixed sub-module - /// .register_static_module("foo::bar::baz", module.clone()) - /// // Multiple registrations to the same partial path is also OK! - /// .register_static_module("foo::bar::hello", module.clone()) - /// .register_static_module("CalcService", module); - /// - /// assert_eq!(engine.eval::("foo::bar::baz::calc(41)")?, 42); - /// assert_eq!(engine.eval::("foo::bar::hello::calc(41)")?, 42); - /// assert_eq!(engine.eval::("CalcService::calc(41)")?, 42); - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_module"))] - pub fn register_static_module( - &mut self, - name: impl AsRef + Into, - module: Shared, - ) -> &mut Self { - fn register_static_module_raw( - root: &mut std::collections::BTreeMap>, - name: impl AsRef + Into, - module: Shared, - ) { - let separator = crate::tokenizer::Token::DoubleColon.syntax(); - - if !name.as_ref().contains(separator.as_ref()) { - if !module.is_indexed() { - // Index the module (making a clone copy if necessary) if it is not indexed - let mut module = crate::func::native::shared_take_or_clone(module); - module.build_index(); - root.insert(name.into(), module.into()); - } else { - root.insert(name.into(), module); - } - } else { - let mut iter = name.as_ref().splitn(2, separator.as_ref()); - let sub_module = iter.next().expect("contains separator").trim(); - let remainder = iter.next().expect("contains separator").trim(); - - if !root.contains_key(sub_module) { - let mut m = Module::new(); - register_static_module_raw(m.sub_modules_mut(), remainder, module); - m.build_index(); - root.insert(sub_module.into(), m.into()); - } else { - let m = root.remove(sub_module).expect("contains sub-module"); - let mut m = crate::func::native::shared_take_or_clone(m); - register_static_module_raw(m.sub_modules_mut(), remainder, module); - m.build_index(); - root.insert(sub_module.into(), m.into()); - } - } - } - - register_static_module_raw(&mut self.global_sub_modules, name, module); - self - } - - /// Compile a string into an [`AST`], which can be used later for evaluation. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// // Compile a script to an AST and store it for later evaluation - /// let ast = engine.compile("40 + 2")?; - /// - /// for _ in 0..42 { - /// assert_eq!(engine.eval_ast::(&ast)?, 42); - /// } - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn compile(&self, script: &str) -> Result { - self.compile_with_scope(&Scope::new(), script) - } - /// Compile a string into an [`AST`] using own scope, which can be used later for evaluation. - /// - /// ## Constants Propagation - /// - /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated - /// throughout the script _including_ functions. This allows functions to be optimized based on - /// dynamic global constants. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_optimize"))] - /// # { - /// use rhai::{Engine, Scope, OptimizationLevel}; - /// - /// let mut engine = Engine::new(); - /// - /// // Create initialized scope - /// let mut scope = Scope::new(); - /// scope.push_constant("x", 42_i64); // 'x' is a constant - /// - /// // Compile a script to an AST and store it for later evaluation. - /// // Notice that `Full` optimization is on, so constants are folded - /// // into function calls and operators. - /// let ast = engine.compile_with_scope(&mut scope, - /// "if x > 40 { x } else { 0 }" // all 'x' are replaced with 42 - /// )?; - /// - /// // Normally this would have failed because no scope is passed into the 'eval_ast' - /// // call and so the variable 'x' does not exist. Here, it passes because the script - /// // has been optimized and all references to 'x' are already gone. - /// assert_eq!(engine.eval_ast::(&ast)?, 42); - /// # } - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn compile_with_scope(&self, scope: &Scope, script: &str) -> Result { - self.compile_scripts_with_scope(scope, &[script]) - } - /// Compile a string into an [`AST`] using own scope, which can be used later for evaluation, - /// embedding all imported modules. - /// - /// Not available under `no_module`. - /// - /// Modules referred by `import` statements containing literal string paths are eagerly resolved - /// via the current [module resolver][crate::ModuleResolver] and embedded into the resultant - /// [`AST`]. When it is evaluated later, `import` statement directly recall pre-resolved - /// [modules][Module] and the resolution process is not performed again. - #[cfg(not(feature = "no_module"))] - pub fn compile_into_self_contained( - &self, - scope: &Scope, - script: &str, - ) -> Result> { - use crate::{ - ast::{ASTNode, Expr, Stmt}, - func::native::shared_take_or_clone, - module::resolvers::StaticModuleResolver, - }; - use std::collections::BTreeSet; - - fn collect_imports( - ast: &AST, - resolver: &StaticModuleResolver, - imports: &mut BTreeSet, - ) { - ast.walk( - &mut |path| match path.last().expect("contains current node") { - // Collect all `import` statements with a string constant path - ASTNode::Stmt(Stmt::Import(Expr::StringConstant(s, _), _, _)) - if !resolver.contains_path(s) && !imports.contains(s.as_str()) => - { - imports.insert(s.clone().into()); - true - } - _ => true, - }, - ); - } - - let mut ast = self.compile_scripts_with_scope(scope, &[script])?; - - if let Some(ref module_resolver) = self.module_resolver { - let mut resolver = StaticModuleResolver::new(); - let mut imports = BTreeSet::new(); - - collect_imports(&ast, &resolver, &mut imports); - - if !imports.is_empty() { - while let Some(path) = imports.iter().next() { - let path = path.clone(); - - match module_resolver.resolve_ast(self, None, &path, Position::NONE) { - Some(Ok(module_ast)) => { - collect_imports(&module_ast, &resolver, &mut imports) - } - Some(err) => return err, - None => (), - } - - let module = module_resolver.resolve(self, None, &path, Position::NONE)?; - let module = shared_take_or_clone(module); - - imports.remove(&path); - resolver.insert(path, module); - } - ast.set_resolver(resolver); - } - } - - Ok(ast) - } - /// When passed a list of strings, first join the strings into one large script, - /// and then compile them into an [`AST`] using own scope, which can be used later for evaluation. - /// - /// The scope is useful for passing constants into the script for optimization - /// when using [`OptimizationLevel::Full`]. - /// - /// ## Note - /// - /// All strings are simply parsed one after another with nothing inserted in between, not even - /// a newline or space. - /// - /// ## Constants Propagation - /// - /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated - /// throughout the script _including_ functions. This allows functions to be optimized based on - /// dynamic global constants. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_optimize"))] - /// # { - /// use rhai::{Engine, Scope, OptimizationLevel}; - /// - /// let mut engine = Engine::new(); - /// - /// // Create initialized scope - /// let mut scope = Scope::new(); - /// scope.push_constant("x", 42_i64); // 'x' is a constant - /// - /// // Compile a script made up of script segments to an AST and store it for later evaluation. - /// // Notice that `Full` optimization is on, so constants are folded - /// // into function calls and operators. - /// let ast = engine.compile_scripts_with_scope(&mut scope, &[ - /// "if x > 40", // all 'x' are replaced with 42 - /// "{ x } el", - /// "se { 0 }" // segments do not need to be valid scripts! - /// ])?; - /// - /// // Normally this would have failed because no scope is passed into the 'eval_ast' - /// // call and so the variable 'x' does not exist. Here, it passes because the script - /// // has been optimized and all references to 'x' are already gone. - /// assert_eq!(engine.eval_ast::(&ast)?, 42); - /// # } - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn compile_scripts_with_scope( - &self, - scope: &Scope, - scripts: &[&str], - ) -> Result { - self.compile_with_scope_and_optimization_level( - scope, - scripts, - #[cfg(not(feature = "no_optimize"))] - self.optimization_level, - ) - } - /// Join a list of strings and compile into an [`AST`] using own scope at a specific optimization level. - /// - /// ## Constants Propagation - /// - /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated - /// throughout the script _including_ functions. This allows functions to be optimized based on - /// dynamic global constants. - #[inline] - pub(crate) fn compile_with_scope_and_optimization_level( - &self, - scope: &Scope, - scripts: &[&str], - #[cfg(not(feature = "no_optimize"))] optimization_level: crate::OptimizationLevel, - ) -> Result { - let (stream, tokenizer_control) = - self.lex_raw(scripts, self.token_mapper.as_ref().map(Box::as_ref)); - let mut state = ParseState::new(self, tokenizer_control); - self.parse( - &mut stream.peekable(), - &mut state, - scope, - #[cfg(not(feature = "no_optimize"))] - optimization_level, - ) - } - /// Read the contents of a file into a string. - #[cfg(not(feature = "no_std"))] - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] - fn read_file(path: std::path::PathBuf) -> Result> { - use std::io::Read; - - let mut f = std::fs::File::open(path.clone()).map_err(|err| { - EvalAltResult::ErrorSystem( - format!("Cannot open script file '{}'", path.to_string_lossy()), - err.into(), - ) - })?; - - let mut contents = String::new(); - - f.read_to_string(&mut contents).map_err(|err| { - EvalAltResult::ErrorSystem( - format!("Cannot read script file '{}'", path.to_string_lossy()), - err.into(), - ) - })?; - - if contents.starts_with("#!") { - // Remove shebang - if let Some(n) = contents.find('\n') { - contents.drain(0..n).count(); - } else { - contents.clear(); - } - }; - - Ok(contents) - } - /// Compile a script file into an [`AST`], which can be used later for evaluation. - /// - /// Not available under `no_std` or `WASM`. - /// - /// # Example - /// - /// ```no_run - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// // Compile a script file to an AST and store it for later evaluation. - /// // Notice that a PathBuf is required which can easily be constructed from a string. - /// let ast = engine.compile_file("script.rhai".into())?; - /// - /// for _ in 0..42 { - /// engine.eval_ast::(&ast)?; - /// } - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_std"))] - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] - #[inline(always)] - pub fn compile_file(&self, path: std::path::PathBuf) -> Result> { - self.compile_file_with_scope(&Scope::new(), path) - } - /// Compile a script file into an [`AST`] using own scope, which can be used later for evaluation. - /// - /// Not available under `no_std` or `WASM`. - /// - /// ## Constants Propagation - /// - /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated - /// throughout the script _including_ functions. This allows functions to be optimized based on - /// dynamic global constants. - /// - /// # Example - /// - /// ```no_run - /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_optimize"))] - /// # { - /// use rhai::{Engine, Scope, OptimizationLevel}; - /// - /// let mut engine = Engine::new(); - /// - /// // Create initialized scope - /// let mut scope = Scope::new(); - /// scope.push_constant("x", 42_i64); // 'x' is a constant - /// - /// // Compile a script to an AST and store it for later evaluation. - /// // Notice that a PathBuf is required which can easily be constructed from a string. - /// let ast = engine.compile_file_with_scope(&mut scope, "script.rhai".into())?; - /// - /// let result = engine.eval_ast::(&ast)?; - /// # } - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_std"))] - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] - #[inline] - pub fn compile_file_with_scope( - &self, - scope: &Scope, - path: std::path::PathBuf, - ) -> Result> { - Self::read_file(path).and_then(|contents| Ok(self.compile_with_scope(scope, &contents)?)) - } - /// Parse a JSON string into an [object map][`Map`]. - /// This is a light-weight alternative to using, say, - /// [`serde_json`](https://crates.io/crates/serde_json) to deserialize the JSON. - /// - /// Not available under `no_object`. - /// - /// The JSON string must be an object hash. It cannot be a simple scalar value. - /// - /// Set `has_null` to `true` in order to map `null` values to `()`. - /// Setting it to `false` will cause an [`ErrorVariableNotFound`][EvalAltResult::ErrorVariableNotFound] error during parsing. - /// - /// # JSON With Sub-Objects - /// - /// This method assumes no sub-objects in the JSON string. That is because the syntax - /// of a JSON sub-object (or object hash), `{ .. }`, is different from Rhai's syntax, `#{ .. }`. - /// Parsing a JSON string with sub-objects will cause a syntax error. - /// - /// If it is certain that the character `{` never appears in any text string within the JSON object, - /// which is a valid assumption for many use cases, then globally replace `{` with `#{` before calling this method. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::{Engine, Map}; - /// - /// let engine = Engine::new(); - /// - /// let map = engine.parse_json( - /// r#"{"a":123, "b":42, "c":{"x":false, "y":true}, "d":null}"# - /// .replace("{", "#{").as_str(), - /// true)?; - /// - /// assert_eq!(map.len(), 4); - /// assert_eq!(map["a"].as_int().expect("a should exist"), 123); - /// assert_eq!(map["b"].as_int().expect("b should exist"), 42); - /// assert!(map["d"].is::<()>()); - /// - /// let c = map["c"].read_lock::().expect("c should exist"); - /// assert_eq!(c["x"].as_bool().expect("x should be bool"), false); - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_object"))] - #[inline(always)] - pub fn parse_json( - &self, - json: impl AsRef, - has_null: bool, - ) -> Result> { - use crate::tokenizer::Token; - - fn parse_json_inner( - engine: &Engine, - json: &str, - has_null: bool, - ) -> Result> { - let mut scope = Scope::new(); - let json_text = json.trim_start(); - let scripts = if json_text.starts_with(Token::MapStart.literal_syntax()) { - [json_text, ""] - } else if json_text.starts_with(Token::LeftBrace.literal_syntax()) { - ["#", json_text] - } else { - return Err(crate::ParseErrorType::MissingToken( - Token::LeftBrace.syntax().into(), - "to start a JSON object hash".into(), - ) - .into_err(Position::new(1, (json.len() - json_text.len() + 1) as u16)) - .into()); - }; - let (stream, tokenizer_control) = engine.lex_raw( - &scripts, - if has_null { - Some(&|token, _, _| { - match token { - // If `null` is present, make sure `null` is treated as a variable - Token::Reserved(s) if &*s == "null" => Token::Identifier(s), - _ => token, - } - }) - } else { - None - }, - ); - let mut state = ParseState::new(engine, tokenizer_control); - let ast = engine.parse_global_expr( - &mut stream.peekable(), - &mut state, - &scope, - #[cfg(not(feature = "no_optimize"))] - crate::OptimizationLevel::None, - )?; - if has_null { - scope.push_constant("null", ()); - } - engine.eval_ast_with_scope(&mut scope, &ast) - } - - parse_json_inner(self, json.as_ref(), has_null) - } - /// Compile a string containing an expression into an [`AST`], - /// which can be used later for evaluation. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// // Compile a script to an AST and store it for later evaluation - /// let ast = engine.compile_expression("40 + 2")?; - /// - /// for _ in 0..42 { - /// assert_eq!(engine.eval_ast::(&ast)?, 42); - /// } - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn compile_expression(&self, script: &str) -> Result { - self.compile_expression_with_scope(&Scope::new(), script) - } - /// Compile a string containing an expression into an [`AST`] using own scope, - /// which can be used later for evaluation. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_optimize"))] - /// # { - /// use rhai::{Engine, Scope, OptimizationLevel}; - /// - /// let mut engine = Engine::new(); - /// - /// // Create initialized scope - /// let mut scope = Scope::new(); - /// scope.push_constant("x", 10_i64); // 'x' is a constant - /// - /// // Compile a script to an AST and store it for later evaluation. - /// // Notice that `Full` optimization is on, so constants are folded - /// // into function calls and operators. - /// let ast = engine.compile_expression_with_scope(&mut scope, - /// "2 + (x + x) * 2" // all 'x' are replaced with 10 - /// )?; - /// - /// // Normally this would have failed because no scope is passed into the 'eval_ast' - /// // call and so the variable 'x' does not exist. Here, it passes because the script - /// // has been optimized and all references to 'x' are already gone. - /// assert_eq!(engine.eval_ast::(&ast)?, 42); - /// # } - /// # Ok(()) - /// # } - /// ``` - #[inline] - pub fn compile_expression_with_scope( - &self, - scope: &Scope, - script: &str, - ) -> Result { - let scripts = [script]; - let (stream, tokenizer_control) = - self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); - - let mut peekable = stream.peekable(); - let mut state = ParseState::new(self, tokenizer_control); - self.parse_global_expr( - &mut peekable, - &mut state, - scope, - #[cfg(not(feature = "no_optimize"))] - self.optimization_level, - ) - } - /// Evaluate a script file. - /// - /// Not available under `no_std` or `WASM`. - /// - /// # Example - /// - /// ```no_run - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// // Notice that a PathBuf is required which can easily be constructed from a string. - /// let result = engine.eval_file::("script.rhai".into())?; - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_std"))] - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] - #[inline] - pub fn eval_file( - &self, - path: std::path::PathBuf, - ) -> Result> { - Self::read_file(path).and_then(|contents| self.eval::(&contents)) - } - /// Evaluate a script file with own scope. - /// - /// Not available under `no_std` or `WASM`. - /// - /// ## Constants Propagation - /// - /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated - /// throughout the script _including_ functions. This allows functions to be optimized based on - /// dynamic global constants. - /// - /// # Example - /// - /// ```no_run - /// # fn main() -> Result<(), Box> { - /// use rhai::{Engine, Scope}; - /// - /// let engine = Engine::new(); - /// - /// // Create initialized scope - /// let mut scope = Scope::new(); - /// scope.push("x", 42_i64); - /// - /// // Notice that a PathBuf is required which can easily be constructed from a string. - /// let result = engine.eval_file_with_scope::(&mut scope, "script.rhai".into())?; - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_std"))] - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] - #[inline] - pub fn eval_file_with_scope( - &self, - scope: &mut Scope, - path: std::path::PathBuf, - ) -> Result> { - Self::read_file(path).and_then(|contents| self.eval_with_scope::(scope, &contents)) - } - /// Evaluate a string. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// assert_eq!(engine.eval::("40 + 2")?, 42); - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn eval(&self, script: &str) -> Result> { - self.eval_with_scope(&mut Scope::new(), script) - } - /// Evaluate a string with own scope. - /// - /// ## Constants Propagation - /// - /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated - /// throughout the script _including_ functions. This allows functions to be optimized based on - /// dynamic global constants. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::{Engine, Scope}; - /// - /// let engine = Engine::new(); - /// - /// // Create initialized scope - /// let mut scope = Scope::new(); - /// scope.push("x", 40_i64); - /// - /// assert_eq!(engine.eval_with_scope::(&mut scope, "x += 2; x")?, 42); - /// assert_eq!(engine.eval_with_scope::(&mut scope, "x += 2; x")?, 44); - /// - /// // The variable in the scope is modified - /// assert_eq!(scope.get_value::("x").expect("variable x should exist"), 44); - /// # Ok(()) - /// # } - /// ``` - #[inline] - pub fn eval_with_scope( - &self, - scope: &mut Scope, - script: &str, - ) -> Result> { - let ast = self.compile_with_scope_and_optimization_level( - scope, - &[script], - #[cfg(not(feature = "no_optimize"))] - self.optimization_level, - )?; - self.eval_ast_with_scope(scope, &ast) - } - /// Evaluate a string containing an expression. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// assert_eq!(engine.eval_expression::("40 + 2")?, 42); - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn eval_expression( - &self, - script: &str, - ) -> Result> { - self.eval_expression_with_scope(&mut Scope::new(), script) - } - /// Evaluate a string containing an expression with own scope. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::{Engine, Scope}; - /// - /// let engine = Engine::new(); - /// - /// // Create initialized scope - /// let mut scope = Scope::new(); - /// scope.push("x", 40_i64); - /// - /// assert_eq!(engine.eval_expression_with_scope::(&mut scope, "x + 2")?, 42); - /// # Ok(()) - /// # } - /// ``` - #[inline] - pub fn eval_expression_with_scope( - &self, - scope: &mut Scope, - script: &str, - ) -> Result> { - let scripts = [script]; - let (stream, tokenizer_control) = - self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); - let mut state = ParseState::new(self, tokenizer_control); - - // No need to optimize a lone expression - let ast = self.parse_global_expr( - &mut stream.peekable(), - &mut state, - scope, - #[cfg(not(feature = "no_optimize"))] - crate::OptimizationLevel::None, - )?; - - self.eval_ast_with_scope(scope, &ast) - } - /// Evaluate an [`AST`]. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// // Compile a script to an AST and store it for later evaluation - /// let ast = engine.compile("40 + 2")?; - /// - /// // Evaluate it - /// assert_eq!(engine.eval_ast::(&ast)?, 42); - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn eval_ast(&self, ast: &AST) -> Result> { - self.eval_ast_with_scope(&mut Scope::new(), ast) - } - /// Evaluate an [`AST`] with own scope. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::{Engine, Scope}; - /// - /// let engine = Engine::new(); - /// - /// // Compile a script to an AST and store it for later evaluation - /// let ast = engine.compile("x + 2")?; - /// - /// // Create initialized scope - /// let mut scope = Scope::new(); - /// scope.push("x", 40_i64); - /// - /// // Compile a script to an AST and store it for later evaluation - /// let ast = engine.compile("x += 2; x")?; - /// - /// // Evaluate it - /// assert_eq!(engine.eval_ast_with_scope::(&mut scope, &ast)?, 42); - /// assert_eq!(engine.eval_ast_with_scope::(&mut scope, &ast)?, 44); - /// - /// // The variable in the scope is modified - /// assert_eq!(scope.get_value::("x").expect("variable x should exist"), 44); - /// # Ok(()) - /// # } - /// ``` - #[inline] - pub fn eval_ast_with_scope( - &self, - scope: &mut Scope, - ast: &AST, - ) -> Result> { - let mods = &mut Imports::new(); - - let result = self.eval_ast_with_scope_raw(scope, mods, ast, 0)?; - - let typ = self.map_type_name(result.type_name()); - - result.try_cast::().ok_or_else(|| { - EvalAltResult::ErrorMismatchOutputType( - self.map_type_name(type_name::()).into(), - typ.into(), - Position::NONE, - ) - .into() - }) - } - /// Evaluate an [`AST`] with own scope. - #[inline] - pub(crate) fn eval_ast_with_scope_raw<'a>( - &self, - scope: &mut Scope, - mods: &mut Imports, - ast: &'a AST, - level: usize, - ) -> RhaiResult { - let mut state = EvalState::new(); - if ast.source_raw().is_some() { - mods.source = ast.source_raw().cloned(); - } - #[cfg(not(feature = "no_module"))] - { - mods.embedded_module_resolver = ast.resolver(); - } - - let statements = ast.statements(); - - if statements.is_empty() { - return Ok(Dynamic::UNIT); - } - - let lib = &[ast.lib()]; - self.eval_global_statements(scope, mods, &mut state, statements, lib, level) - } - /// Evaluate a file, returning any error (if any). - /// - /// Not available under `no_std` or `WASM`. - #[cfg(not(feature = "no_std"))] - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] - #[inline] - pub fn run_file(&self, path: std::path::PathBuf) -> Result<(), Box> { - Self::read_file(path).and_then(|contents| self.run(&contents)) - } - /// Evaluate a file with own scope, returning any error (if any). - /// - /// Not available under `no_std` or `WASM`. - /// - /// ## Constants Propagation - /// - /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated - /// throughout the script _including_ functions. This allows functions to be optimized based on - /// dynamic global constants. - #[cfg(not(feature = "no_std"))] - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] - #[inline] - pub fn run_file_with_scope( - &self, - scope: &mut Scope, - path: std::path::PathBuf, - ) -> Result<(), Box> { - Self::read_file(path).and_then(|contents| self.run_with_scope(scope, &contents)) - } - /// Evaluate a script, returning any error (if any). - #[inline(always)] - pub fn run(&self, script: &str) -> Result<(), Box> { - self.run_with_scope(&mut Scope::new(), script) - } - /// Evaluate a script with own scope, returning any error (if any). - /// - /// ## Constants Propagation - /// - /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated - /// throughout the script _including_ functions. This allows functions to be optimized based on - /// dynamic global constants. - #[inline] - pub fn run_with_scope( - &self, - scope: &mut Scope, - script: &str, - ) -> Result<(), Box> { - let scripts = [script]; - let (stream, tokenizer_control) = - self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); - let mut state = ParseState::new(self, tokenizer_control); - - let ast = self.parse( - &mut stream.peekable(), - &mut state, - scope, - #[cfg(not(feature = "no_optimize"))] - self.optimization_level, - )?; - - self.run_ast_with_scope(scope, &ast) - } - /// Evaluate an AST, returning any error (if any). - #[inline(always)] - pub fn run_ast(&self, ast: &AST) -> Result<(), Box> { - self.run_ast_with_scope(&mut Scope::new(), ast) - } - /// Evaluate an [`AST`] with own scope, returning any error (if any). - #[inline] - pub fn run_ast_with_scope( - &self, - scope: &mut Scope, - ast: &AST, - ) -> Result<(), Box> { - let mods = &mut Imports::new(); - let mut state = EvalState::new(); - if ast.source_raw().is_some() { - mods.source = ast.source_raw().cloned(); - } - #[cfg(not(feature = "no_module"))] - { - mods.embedded_module_resolver = ast.resolver(); - } - - let statements = ast.statements(); - if !statements.is_empty() { - let lib = &[ast.lib()]; - self.eval_global_statements(scope, mods, &mut state, statements, lib, 0)?; - } - Ok(()) - } - /// Call a script function defined in an [`AST`] with multiple arguments. - /// Arguments are passed as a tuple. - /// - /// Not available under `no_function`. - /// - /// The [`AST`] is evaluated before calling the function. - /// This allows a script to load the necessary modules. - /// This is usually desired. If not, a specialized [`AST`] can be prepared that contains only - /// function definitions without any body script via [`AST::clear_statements`]. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_function"))] - /// # { - /// use rhai::{Engine, Scope}; - /// - /// let engine = Engine::new(); - /// - /// let ast = engine.compile(" - /// fn add(x, y) { len(x) + y + foo } - /// fn add1(x) { len(x) + 1 + foo } - /// fn bar() { foo/2 } - /// ")?; - /// - /// let mut scope = Scope::new(); - /// scope.push("foo", 42_i64); - /// - /// // Call the script-defined function - /// let result: i64 = engine.call_fn(&mut scope, &ast, "add", ( "abc", 123_i64 ) )?; - /// assert_eq!(result, 168); - /// - /// let result: i64 = engine.call_fn(&mut scope, &ast, "add1", ( "abc", ) )?; - /// // ^^^^^^^^^^ tuple of one - /// assert_eq!(result, 46); - /// - /// let result: i64 = engine.call_fn(&mut scope, &ast, "bar", () )?; - /// assert_eq!(result, 21); - /// # } - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_function"))] - #[inline] - pub fn call_fn( - &self, - scope: &mut Scope, - ast: &AST, - name: impl AsRef, - args: impl crate::FuncArgs, - ) -> Result> { - let mut arg_values = crate::StaticVec::new(); - args.parse(&mut arg_values); - - let result = self.call_fn_raw(scope, ast, true, true, name, None, arg_values)?; - - let typ = self.map_type_name(result.type_name()); - - result.try_cast().ok_or_else(|| { - EvalAltResult::ErrorMismatchOutputType( - self.map_type_name(type_name::()).into(), - typ.into(), - Position::NONE, - ) - .into() - }) - } - /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments - /// and the following options: - /// - /// * whether to evaluate the [`AST`] to load necessary modules before calling the function - /// * whether to rewind the [`Scope`] after the function call - /// * a value for binding to the `this` pointer (if any) - /// - /// Not available under `no_function`. - /// - /// # WARNING - /// - /// All the arguments are _consumed_, meaning that they're replaced by `()`. - /// This is to avoid unnecessarily cloning the arguments. - /// Do not use the arguments after this call. If they are needed afterwards, - /// clone them _before_ calling this function. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_function"))] - /// # { - /// use rhai::{Engine, Scope, Dynamic}; - /// - /// let engine = Engine::new(); - /// - /// let ast = engine.compile(" - /// fn add(x, y) { len(x) + y + foo } - /// fn add1(x) { len(x) + 1 + foo } - /// fn bar() { foo/2 } - /// fn action(x) { this += x; } // function using 'this' pointer - /// fn decl(x) { let hello = x; } // declaring variables - /// ")?; - /// - /// let mut scope = Scope::new(); - /// scope.push("foo", 42_i64); - /// - /// // Call the script-defined function - /// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "add", None, [ "abc".into(), 123_i64.into() ])?; - /// // ^^^^ no 'this' pointer - /// assert_eq!(result.cast::(), 168); - /// - /// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "add1", None, [ "abc".into() ])?; - /// assert_eq!(result.cast::(), 46); - /// - /// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "bar", None, [])?; - /// assert_eq!(result.cast::(), 21); - /// - /// let mut value: Dynamic = 1_i64.into(); - /// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "action", Some(&mut value), [ 41_i64.into() ])?; - /// // ^^^^^^^^^^^^^^^^ binding the 'this' pointer - /// assert_eq!(value.as_int().expect("value should be INT"), 42); - /// - /// engine.call_fn_raw(&mut scope, &ast, true, false, "decl", None, [ 42_i64.into() ])?; - /// // ^^^^^ do not rewind scope - /// assert_eq!(scope.get_value::("hello").unwrap(), 42); - /// # } - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_function"))] - #[inline] - pub fn call_fn_raw( - &self, - scope: &mut Scope, - ast: &AST, - eval_ast: bool, - rewind_scope: bool, - name: impl AsRef, - this_ptr: Option<&mut Dynamic>, - arg_values: impl AsMut<[Dynamic]>, - ) -> RhaiResult { - let state = &mut EvalState::new(); - let mods = &mut Imports::new(); - let statements = ast.statements(); - - let orig_scope_len = scope.len(); - - if eval_ast && !statements.is_empty() { - // Make sure new variables introduced at global level do not _spill_ into the function call - self.eval_global_statements(scope, mods, state, statements, &[ast.lib()], 0)?; - - if rewind_scope { - scope.rewind(orig_scope_len); - } - } - - let name = name.as_ref(); - let mut this_ptr = this_ptr; - let mut arg_values = arg_values; - let mut args: crate::StaticVec<_> = arg_values.as_mut().iter_mut().collect(); - - let fn_def = ast - .lib() - .get_script_fn(name, args.len()) - .ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::NONE))?; - - // Check for data race. - #[cfg(not(feature = "no_closure"))] - crate::func::call::ensure_no_data_race(name, &mut args, false)?; - - let result = self.call_script_fn( - scope, - mods, - state, - &[ast.lib()], - &mut this_ptr, - fn_def, - &mut args, - Position::NONE, - rewind_scope, - 0, - ); - - result - } - /// Optimize the [`AST`] with constants defined in an external Scope. - /// An optimized copy of the [`AST`] is returned while the original [`AST`] is consumed. - /// - /// Not available under `no_optimize`. - /// - /// Although optimization is performed by default during compilation, sometimes it is necessary to - /// _re_-optimize an [`AST`]. For example, when working with constants that are passed in via an - /// external scope, it will be more efficient to optimize the [`AST`] once again to take advantage - /// of the new constants. - /// - /// With this method, it is no longer necessary to recompile a large script. - /// The script [`AST`] can be compiled just once. Before evaluation, - /// constants are passed into the [`Engine`] via an external scope - /// (i.e. with [`Scope::push_constant`]). - /// Then, the [`AST`] is cloned and the copy re-optimized before running. - #[cfg(not(feature = "no_optimize"))] - #[inline] - #[must_use] - pub fn optimize_ast( - &self, - scope: &Scope, - ast: AST, - optimization_level: crate::OptimizationLevel, - ) -> AST { - let mut ast = ast; - - #[cfg(not(feature = "no_function"))] - let lib = ast - .lib() - .iter_fn() - .filter(|f| f.func.is_script()) - .map(|f| { - f.func - .get_script_fn_def() - .expect("scripted function") - .clone() - }) - .collect(); - - #[cfg(feature = "no_function")] - let lib = crate::StaticVec::new(); - - let statements = std::mem::take(ast.statements_mut()); - - crate::optimizer::optimize_into_ast(self, scope, statements, lib, optimization_level) - } - /// _(metadata)_ Generate a list of all registered functions. - /// Exported under the `metadata` feature only. - /// - /// Functions from the following sources are included, in order: - /// 1) Functions registered into the global namespace - /// 2) Functions in registered sub-modules - /// 3) Functions in packages (optional) - #[cfg(feature = "metadata")] - #[inline] - #[must_use] - pub fn gen_fn_signatures(&self, include_packages: bool) -> Vec { - let mut signatures = Vec::with_capacity(64); - - signatures.extend(self.global_namespace().gen_fn_signatures()); - - self.global_sub_modules.iter().for_each(|(name, m)| { - signatures.extend(m.gen_fn_signatures().map(|f| format!("{}::{}", name, f))) - }); - - if include_packages { - signatures.extend( - self.global_modules - .iter() - .skip(1) - .flat_map(|m| m.gen_fn_signatures()), - ); - } - - signatures - } - /// Provide a callback that will be invoked before each variable access. - /// - /// # Callback Function Signature - /// - /// The callback function signature takes the following form: - /// - /// > `Fn(name: &str, index: usize, context: &EvalContext)` - /// > ` -> Result, Box> + 'static` - /// - /// where: - /// * `index`: an offset from the bottom of the current [`Scope`] that the variable is supposed - /// to reside. Offsets start from 1, with 1 meaning the last variable in the current - /// [`Scope`]. Essentially the correct variable is at position `scope.len() - index`. - /// If `index` is zero, then there is no pre-calculated offset position and a search through the - /// current [`Scope`] must be performed. - /// - /// * `context`: the current [evaluation context][`EvalContext`]. - /// - /// ## Return value - /// - /// * `Ok(None)`: continue with normal variable access. - /// * `Ok(Some(Dynamic))`: the variable's value. - /// - /// ## Raising errors - /// - /// Return `Err(...)` if there is an error. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let mut engine = Engine::new(); - /// - /// // Register a variable resolver. - /// engine.on_var(|name, _, _| { - /// match name { - /// "MYSTIC_NUMBER" => Ok(Some(42_i64.into())), - /// _ => Ok(None) - /// } - /// }); - /// - /// engine.eval::("MYSTIC_NUMBER")?; - /// - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn on_var( - &mut self, - callback: impl Fn(&str, usize, &EvalContext) -> Result, Box> - + SendSync - + 'static, - ) -> &mut Self { - self.resolve_var = Some(Box::new(callback)); - self - } - /// _(internals)_ Provide a callback that will be invoked during parsing to remap certain tokens. - /// Exported under the `internals` feature only. - /// - /// # Callback Function Signature - /// - /// The callback function signature takes the following form: - /// - /// > `Fn(token: Token, pos: Position, state: &TokenizeState) -> Token` - /// - /// where: - /// * [`token`][crate::tokenizer::Token]: current token parsed - /// * [`pos`][`Position`]: location of the token - /// * [`state`][crate::tokenizer::TokenizeState]: current state of the tokenizer - /// - /// ## Raising errors - /// - /// It is possible to raise a parsing error by returning - /// [`Token::LexError`][crate::tokenizer::Token::LexError] as the mapped token. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::{Engine, Token}; - /// - /// let mut engine = Engine::new(); - /// - /// // Register a token mapper. - /// engine.on_parse_token(|token, _, _| { - /// match token { - /// // Convert all integer literals to strings - /// Token::IntegerConstant(n) => Token::StringConstant(n.to_string().into()), - /// // Convert 'begin' .. 'end' to '{' .. '}' - /// Token::Identifier(s) if &*s == "begin" => Token::LeftBrace, - /// Token::Identifier(s) if &*s == "end" => Token::RightBrace, - /// // Pass through all other tokens unchanged - /// _ => token - /// } - /// }); - /// - /// assert_eq!(engine.eval::("42")?, "42"); - /// assert_eq!(engine.eval::("true")?, true); - /// assert_eq!(engine.eval::("let x = 42; begin let x = 0; end; x")?, "42"); - /// - /// # Ok(()) - /// # } - /// ``` - #[cfg(feature = "internals")] - #[inline(always)] - pub fn on_parse_token( - &mut self, - callback: impl Fn( - crate::tokenizer::Token, - Position, - &crate::tokenizer::TokenizeState, - ) -> crate::tokenizer::Token - + SendSync - + 'static, - ) -> &mut Self { - self.token_mapper = Some(Box::new(callback)); - self - } - /// Register a callback for script evaluation progress. - /// - /// Not available under `unchecked`. - /// - /// # Callback Function Signature - /// - /// The callback function signature takes the following form: - /// - /// > `Fn(counter: u64) -> Option` - /// - /// ## Return value - /// - /// * `None`: continue running the script. - /// * `Some(Dynamic)`: terminate the script with the specified exception value. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # use std::sync::RwLock; - /// # use std::sync::Arc; - /// use rhai::Engine; - /// - /// let result = Arc::new(RwLock::new(0_u64)); - /// let logger = result.clone(); - /// - /// let mut engine = Engine::new(); - /// - /// engine.on_progress(move |ops| { - /// if ops > 10000 { - /// Some("Over 10,000 operations!".into()) - /// } else if ops % 800 == 0 { - /// *logger.write().unwrap() = ops; - /// None - /// } else { - /// None - /// } - /// }); - /// - /// engine.run("for x in range(0, 50000) {}") - /// .expect_err("should error"); - /// - /// assert_eq!(*result.read().unwrap(), 9600); - /// - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "unchecked"))] - #[inline(always)] - pub fn on_progress( - &mut self, - callback: impl Fn(u64) -> Option + SendSync + 'static, - ) -> &mut Self { - self.progress = Some(Box::new(callback)); - self - } - /// Override default action of `print` (print to stdout using [`println!`]) - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # use std::sync::RwLock; - /// # use std::sync::Arc; - /// use rhai::Engine; - /// - /// let result = Arc::new(RwLock::new(String::new())); - /// - /// let mut engine = Engine::new(); - /// - /// // Override action of 'print' function - /// let logger = result.clone(); - /// engine.on_print(move |s| logger.write().unwrap().push_str(s)); - /// - /// engine.run("print(40 + 2);")?; - /// - /// assert_eq!(*result.read().unwrap(), "42"); - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn on_print(&mut self, callback: impl Fn(&str) + SendSync + 'static) -> &mut Self { - self.print = Some(Box::new(callback)); - self - } - /// Override default action of `debug` (print to stdout using [`println!`]) - /// - /// # Callback Function Signature - /// - /// The callback function signature passed takes the following form: - /// - /// > `Fn(text: &str, source: Option<&str>, pos: Position)` - /// - /// where: - /// * `text`: the text to display - /// * `source`: current source, if any - /// * [`pos`][`Position`]: location of the `debug` call - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # use std::sync::RwLock; - /// # use std::sync::Arc; - /// use rhai::Engine; - /// - /// let result = Arc::new(RwLock::new(String::new())); - /// - /// let mut engine = Engine::new(); - /// - /// // Override action of 'print' function - /// let logger = result.clone(); - /// engine.on_debug(move |s, src, pos| logger.write().unwrap().push_str( - /// &format!("{} @ {:?} > {}", src.unwrap_or("unknown"), pos, s) - /// )); - /// - /// let mut ast = engine.compile(r#"let x = "hello"; debug(x);"#)?; - /// ast.set_source("world"); - /// engine.run_ast(&ast)?; - /// - /// #[cfg(not(feature = "no_position"))] - /// assert_eq!(*result.read().unwrap(), r#"world @ 1:18 > "hello""#); - /// #[cfg(feature = "no_position")] - /// assert_eq!(*result.read().unwrap(), r#"world @ none > "hello""#); - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn on_debug( - &mut self, - callback: impl Fn(&str, Option<&str>, Position) + SendSync + 'static, - ) -> &mut Self { - self.debug = Some(Box::new(callback)); - self - } -} diff --git a/src/api/register.rs b/src/api/register.rs new file mode 100644 index 00000000..8086bf08 --- /dev/null +++ b/src/api/register.rs @@ -0,0 +1,999 @@ +//! Module that defines the public function/module registration API of [`Engine`]. + +use crate::func::{FnCallArgs, RegisterNativeFunction, SendSync}; +use crate::types::dynamic::Variant; +use crate::{ + Engine, EvalAltResult, FnAccess, FnNamespace, Identifier, Module, NativeCallContext, Shared, +}; +use std::any::{type_name, TypeId}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +#[cfg(not(feature = "no_index"))] +use crate::Array; + +#[cfg(not(feature = "no_object"))] +use crate::Map; + +impl Engine { + /// Get the global namespace module (which is the last module in `global_modules`). + #[inline(always)] + #[allow(dead_code)] + pub(crate) fn global_namespace(&self) -> &Module { + self.global_modules.first().expect("not empty") + } + /// Get a mutable reference to the global namespace module + /// (which is the first module in `global_modules`). + #[inline(always)] + pub(crate) fn global_namespace_mut(&mut self) -> &mut Module { + Shared::get_mut(self.global_modules.first_mut().expect("not empty")).expect("not shared") + } + /// Register a custom function with the [`Engine`]. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// // Normal function + /// fn add(x: i64, y: i64) -> i64 { + /// x + y + /// } + /// + /// let mut engine = Engine::new(); + /// + /// engine.register_fn("add", add); + /// + /// assert_eq!(engine.eval::("add(40, 2)")?, 42); + /// + /// // You can also register a closure. + /// engine.register_fn("sub", |x: i64, y: i64| x - y ); + /// + /// assert_eq!(engine.eval::("sub(44, 2)")?, 42); + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn register_fn(&mut self, name: N, func: F) -> &mut Self + where + N: AsRef + Into, + F: RegisterNativeFunction, + { + let param_types = F::param_types(); + + #[cfg(feature = "metadata")] + let mut param_type_names: crate::StaticVec<_> = F::param_names() + .iter() + .map(|ty| format!("_: {}", self.map_type_name(ty))) + .collect(); + + #[cfg(feature = "metadata")] + if F::return_type() != TypeId::of::<()>() { + param_type_names.push(self.map_type_name(F::return_type_name()).into()); + } + + #[cfg(feature = "metadata")] + let param_type_names: Option> = + Some(param_type_names.iter().map(|ty| ty.as_str()).collect()); + + #[cfg(not(feature = "metadata"))] + let param_type_names: Option<[&str; 0]> = None; + + self.global_namespace_mut().set_fn( + name, + FnNamespace::Global, + FnAccess::Public, + param_type_names.as_ref().map(|v| v.as_ref()), + ¶m_types, + func.into_callable_function(), + ); + self + } + /// Register a custom fallible function with the [`Engine`]. + /// + /// # Example + /// + /// ``` + /// use rhai::{Engine, EvalAltResult}; + /// + /// // Normal function + /// fn div(x: i64, y: i64) -> Result> { + /// if y == 0 { + /// // '.into()' automatically converts to 'Box' + /// Err("division by zero!".into()) + /// } else { + /// Ok(x / y) + /// } + /// } + /// + /// let mut engine = Engine::new(); + /// + /// engine.register_result_fn("div", div); + /// + /// engine.eval::("div(42, 0)") + /// .expect_err("expecting division by zero error!"); + /// ``` + #[inline] + pub fn register_result_fn(&mut self, name: N, func: F) -> &mut Self + where + N: AsRef + Into, + F: RegisterNativeFunction>>, + { + let param_types = F::param_types(); + + #[cfg(feature = "metadata")] + let param_type_names: crate::StaticVec<_> = F::param_names() + .iter() + .map(|ty| format!("_: {}", self.map_type_name(ty))) + .chain(std::iter::once( + self.map_type_name(F::return_type_name()).into(), + )) + .collect(); + + #[cfg(feature = "metadata")] + let param_type_names: Option> = + Some(param_type_names.iter().map(|ty| ty.as_str()).collect()); + + #[cfg(not(feature = "metadata"))] + let param_type_names: Option<[&str; 0]> = None; + + self.global_namespace_mut().set_fn( + name, + FnNamespace::Global, + FnAccess::Public, + param_type_names.as_ref().map(|v| v.as_ref()), + ¶m_types, + func.into_callable_function(), + ); + self + } + /// Register a function of the [`Engine`]. + /// + /// # WARNING - Low Level API + /// + /// This function is very low level. It takes a list of [`TypeId`][std::any::TypeId]'s indicating the actual types of the parameters. + /// + /// Arguments are simply passed in as a mutable array of [`&mut Dynamic`][Dynamic], + /// The arguments are guaranteed to be of the correct types matching the [`TypeId`][std::any::TypeId]'s. + /// + /// To access a primary argument value (i.e. cloning is cheap), use: `args[n].as_xxx().unwrap()` + /// + /// To access an argument value and avoid cloning, use `std::mem::take(args[n]).cast::()`. + /// Notice that this will _consume_ the argument, replacing it with `()`. + /// + /// To access the first mutable parameter, use `args.get_mut(0).unwrap()` + #[deprecated = "this function is volatile and may change"] + #[inline(always)] + pub fn register_raw_fn( + &mut self, + name: N, + arg_types: &[TypeId], + func: impl Fn(NativeCallContext, &mut FnCallArgs) -> Result> + + SendSync + + 'static, + ) -> &mut Self + where + N: AsRef + Into, + T: Variant + Clone, + { + self.global_namespace_mut().set_raw_fn( + name, + FnNamespace::Global, + FnAccess::Public, + arg_types, + func, + ); + self + } + /// Register a custom type for use with the [`Engine`]. + /// The type must implement [`Clone`]. + /// + /// # Example + /// + /// ``` + /// #[derive(Debug, Clone, Eq, PartialEq)] + /// struct TestStruct { + /// field: i64 + /// } + /// + /// impl TestStruct { + /// fn new() -> Self { Self { field: 1 } } + /// fn update(&mut self, offset: i64) { self.field += offset; } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// // Register API for the custom type. + /// engine + /// .register_type::() + /// .register_fn("new_ts", TestStruct::new) + /// // Use `register_fn` to register methods on the type. + /// .register_fn("update", TestStruct::update); + /// + /// # #[cfg(not(feature = "no_object"))] + /// assert_eq!( + /// engine.eval::("let x = new_ts(); x.update(41); x")?, + /// TestStruct { field: 42 } + /// ); + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn register_type(&mut self) -> &mut Self { + self.register_type_with_name::(type_name::()) + } + /// Register a custom type for use with the [`Engine`], with a pretty-print name + /// for the `type_of` function. The type must implement [`Clone`]. + /// + /// # Example + /// + /// ``` + /// #[derive(Clone)] + /// struct TestStruct { + /// field: i64 + /// } + /// + /// impl TestStruct { + /// fn new() -> Self { Self { field: 1 } } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// // Register API for the custom type. + /// engine + /// .register_type::() + /// .register_fn("new_ts", TestStruct::new); + /// + /// assert_eq!( + /// engine.eval::("let x = new_ts(); type_of(x)")?, + /// "rust_out::TestStruct" + /// ); + /// + /// // Re-register the custom type with a name. + /// engine.register_type_with_name::("Hello"); + /// + /// assert_eq!( + /// engine.eval::("let x = new_ts(); type_of(x)")?, + /// "Hello" + /// ); + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn register_type_with_name(&mut self, name: &str) -> &mut Self { + // Add the pretty-print type name into the map + self.type_names + .insert(type_name::().into(), Box::new(name.into())); + self + } + /// Register an type iterator for an iterable type with the [`Engine`]. + /// This is an advanced feature. + #[inline(always)] + pub fn register_iterator(&mut self) -> &mut Self + where + T: Variant + Clone + IntoIterator, + ::Item: Variant + Clone, + { + self.global_namespace_mut().set_iterable::(); + self + } + /// Register a getter function for a member of a registered type with the [`Engine`]. + /// + /// The function signature must start with `&mut self` and not `&self`. + /// + /// Not available under `no_object`. + /// + /// # Example + /// + /// ``` + /// #[derive(Clone)] + /// struct TestStruct { + /// field: i64 + /// } + /// + /// impl TestStruct { + /// fn new() -> Self { Self { field: 1 } } + /// // Even a getter must start with `&mut self` and not `&self`. + /// fn get_field(&mut self) -> i64 { self.field } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// // Register API for the custom type. + /// engine + /// .register_type::() + /// .register_fn("new_ts", TestStruct::new) + /// // Register a getter on a property (notice it doesn't have to be the same name). + /// .register_get("xyz", TestStruct::get_field); + /// + /// assert_eq!(engine.eval::("let a = new_ts(); a.xyz")?, 1); + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_object"))] + #[inline(always)] + pub fn register_get( + &mut self, + name: &str, + get_fn: impl Fn(&mut T) -> V + SendSync + 'static, + ) -> &mut Self { + self.register_fn(&crate::engine::make_getter(name), get_fn) + } + /// Register a getter function for a member of a registered type with the [`Engine`]. + /// + /// The function signature must start with `&mut self` and not `&self`. + /// + /// Not available under `no_object`. + /// + /// # Example + /// + /// ``` + /// use rhai::{Engine, Dynamic, EvalAltResult}; + /// + /// #[derive(Clone)] + /// struct TestStruct { + /// field: i64 + /// } + /// + /// impl TestStruct { + /// fn new() -> Self { Self { field: 1 } } + /// // Even a getter must start with `&mut self` and not `&self`. + /// fn get_field(&mut self) -> Result> { + /// Ok(self.field) + /// } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// let mut engine = Engine::new(); + /// + /// // Register API for the custom type. + /// engine + /// .register_type::() + /// .register_fn("new_ts", TestStruct::new) + /// // Register a getter on a property (notice it doesn't have to be the same name). + /// .register_get_result("xyz", TestStruct::get_field); + /// + /// assert_eq!(engine.eval::("let a = new_ts(); a.xyz")?, 1); + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_object"))] + #[inline(always)] + pub fn register_get_result( + &mut self, + name: &str, + get_fn: impl Fn(&mut T) -> Result> + SendSync + 'static, + ) -> &mut Self { + self.register_result_fn(&crate::engine::make_getter(name), get_fn) + } + /// Register a setter function for a member of a registered type with the [`Engine`]. + /// + /// Not available under `no_object`. + /// + /// # Example + /// + /// ``` + /// #[derive(Debug, Clone, Eq, PartialEq)] + /// struct TestStruct { + /// field: i64 + /// } + /// + /// impl TestStruct { + /// fn new() -> Self { Self { field: 1 } } + /// fn set_field(&mut self, new_val: i64) { self.field = new_val; } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// // Register API for the custom type. + /// engine + /// .register_type::() + /// .register_fn("new_ts", TestStruct::new) + /// // Register a setter on a property (notice it doesn't have to be the same name) + /// .register_set("xyz", TestStruct::set_field); + /// + /// // Notice that, with a getter, there is no way to get the property value + /// assert_eq!( + /// engine.eval::("let a = new_ts(); a.xyz = 42; a")?, + /// TestStruct { field: 42 } + /// ); + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_object"))] + #[inline(always)] + pub fn register_set( + &mut self, + name: &str, + set_fn: impl Fn(&mut T, V) + SendSync + 'static, + ) -> &mut Self { + self.register_fn(&crate::engine::make_setter(name), set_fn) + } + /// Register a setter function for a member of a registered type with the [`Engine`]. + /// + /// Not available under `no_object`. + /// + /// # Example + /// + /// ``` + /// use rhai::{Engine, Dynamic, EvalAltResult}; + /// + /// #[derive(Debug, Clone, Eq, PartialEq)] + /// struct TestStruct { + /// field: i64 + /// } + /// + /// impl TestStruct { + /// fn new() -> Self { Self { field: 1 } } + /// fn set_field(&mut self, new_val: i64) -> Result<(), Box> { + /// self.field = new_val; + /// Ok(()) + /// } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// let mut engine = Engine::new(); + /// + /// // Register API for the custom type. + /// engine + /// .register_type::() + /// .register_fn("new_ts", TestStruct::new) + /// // Register a setter on a property (notice it doesn't have to be the same name) + /// .register_set_result("xyz", TestStruct::set_field); + /// + /// // Notice that, with a getter, there is no way to get the property value + /// assert_eq!( + /// engine.eval::("let a = new_ts(); a.xyz = 42; a")?, + /// TestStruct { field: 42 } + /// ); + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_object"))] + #[inline(always)] + pub fn register_set_result( + &mut self, + name: &str, + set_fn: impl Fn(&mut T, V) -> Result<(), Box> + SendSync + 'static, + ) -> &mut Self { + self.register_result_fn(&crate::engine::make_setter(name), set_fn) + } + /// Short-hand for registering both getter and setter functions + /// of a registered type with the [`Engine`]. + /// + /// All function signatures must start with `&mut self` and not `&self`. + /// + /// Not available under `no_object`. + /// + /// # Example + /// + /// ``` + /// #[derive(Clone)] + /// struct TestStruct { + /// field: i64 + /// } + /// + /// impl TestStruct { + /// fn new() -> Self { Self { field: 1 } } + /// // Even a getter must start with `&mut self` and not `&self`. + /// fn get_field(&mut self) -> i64 { self.field } + /// fn set_field(&mut self, new_val: i64) { self.field = new_val; } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// // Register API for the custom type. + /// engine + /// .register_type::() + /// .register_fn("new_ts", TestStruct::new) + /// // Register both a getter and a setter on a property + /// // (notice it doesn't have to be the same name) + /// .register_get_set("xyz", TestStruct::get_field, TestStruct::set_field); + /// + /// assert_eq!(engine.eval::("let a = new_ts(); a.xyz = 42; a.xyz")?, 42); + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_object"))] + #[inline(always)] + pub fn register_get_set( + &mut self, + name: &str, + get_fn: impl Fn(&mut T) -> V + SendSync + 'static, + set_fn: impl Fn(&mut T, V) + SendSync + 'static, + ) -> &mut Self { + self.register_get(name, get_fn).register_set(name, set_fn) + } + /// Register an index getter for a custom type with the [`Engine`]. + /// + /// The function signature must start with `&mut self` and not `&self`. + /// + /// Not available under both `no_index` and `no_object`. + /// + /// # Panics + /// + /// Panics if the type is [`Array`], [`Map`], [`String`], + /// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT]. + /// Indexers for arrays, object maps, strings and integers cannot be registered. + /// + /// # Example + /// + /// ``` + /// #[derive(Clone)] + /// struct TestStruct { + /// fields: Vec + /// } + /// + /// impl TestStruct { + /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } + /// // Even a getter must start with `&mut self` and not `&self`. + /// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// // Register API for the custom type. + /// # #[cfg(not(feature = "no_object"))] + /// engine.register_type::(); + /// + /// engine + /// .register_fn("new_ts", TestStruct::new) + /// // Register an indexer. + /// .register_indexer_get(TestStruct::get_field); + /// + /// # #[cfg(not(feature = "no_index"))] + /// assert_eq!(engine.eval::("let a = new_ts(); a[2]")?, 3); + /// # Ok(()) + /// # } + /// ``` + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] + #[inline] + pub fn register_indexer_get( + &mut self, + get_fn: impl Fn(&mut T, X) -> V + SendSync + 'static, + ) -> &mut Self { + #[cfg(not(feature = "no_index"))] + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for arrays."); + } + #[cfg(not(feature = "no_object"))] + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for object maps."); + } + if TypeId::of::() == TypeId::of::() + || TypeId::of::() == TypeId::of::<&str>() + || TypeId::of::() == TypeId::of::() + { + panic!("Cannot register indexer for strings."); + } + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for integers."); + } + + self.register_fn(crate::engine::FN_IDX_GET, get_fn) + } + /// Register an index getter for a custom type with the [`Engine`]. + /// + /// The function signature must start with `&mut self` and not `&self`. + /// + /// Not available under both `no_index` and `no_object`. + /// + /// # Panics + /// + /// Panics if the type is [`Array`], [`Map`], [`String`], + /// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT]. + /// Indexers for arrays, object maps, strings and integers cannot be registered. + /// + /// # Example + /// + /// ``` + /// use rhai::{Engine, Dynamic, EvalAltResult}; + /// + /// #[derive(Clone)] + /// struct TestStruct { + /// fields: Vec + /// } + /// + /// impl TestStruct { + /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } + /// // Even a getter must start with `&mut self` and not `&self`. + /// fn get_field(&mut self, index: i64) -> Result> { + /// Ok(self.fields[index as usize]) + /// } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// let mut engine = Engine::new(); + /// + /// // Register API for the custom type. + /// # #[cfg(not(feature = "no_object"))] + /// engine.register_type::(); + /// + /// engine + /// .register_fn("new_ts", TestStruct::new) + /// // Register an indexer. + /// .register_indexer_get_result(TestStruct::get_field); + /// + /// # #[cfg(not(feature = "no_index"))] + /// assert_eq!(engine.eval::("let a = new_ts(); a[2]")?, 3); + /// # Ok(()) + /// # } + /// ``` + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] + #[inline] + pub fn register_indexer_get_result< + T: Variant + Clone, + X: Variant + Clone, + V: Variant + Clone, + >( + &mut self, + get_fn: impl Fn(&mut T, X) -> Result> + SendSync + 'static, + ) -> &mut Self { + #[cfg(not(feature = "no_index"))] + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for arrays."); + } + #[cfg(not(feature = "no_object"))] + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for object maps."); + } + if TypeId::of::() == TypeId::of::() + || TypeId::of::() == TypeId::of::<&str>() + || TypeId::of::() == TypeId::of::() + { + panic!("Cannot register indexer for strings."); + } + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for integers."); + } + + self.register_result_fn(crate::engine::FN_IDX_GET, get_fn) + } + /// Register an index setter for a custom type with the [`Engine`]. + /// + /// Not available under both `no_index` and `no_object`. + /// + /// # Panics + /// + /// Panics if the type is [`Array`], [`Map`], [`String`], + /// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT]. + /// Indexers for arrays, object maps, strings and integers cannot be registered. + /// + /// # Example + /// + /// ``` + /// #[derive(Clone)] + /// struct TestStruct { + /// fields: Vec + /// } + /// + /// impl TestStruct { + /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } + /// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// // Register API for the custom type. + /// # #[cfg(not(feature = "no_object"))] + /// engine.register_type::(); + /// + /// engine + /// .register_fn("new_ts", TestStruct::new) + /// // Register an indexer. + /// .register_indexer_set(TestStruct::set_field); + /// + /// # #[cfg(not(feature = "no_index"))] + /// assert_eq!( + /// engine.eval::("let a = new_ts(); a[2] = 42; a")?.fields[2], + /// 42 + /// ); + /// # Ok(()) + /// # } + /// ``` + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] + #[inline] + pub fn register_indexer_set( + &mut self, + set_fn: impl Fn(&mut T, X, V) + SendSync + 'static, + ) -> &mut Self { + #[cfg(not(feature = "no_index"))] + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for arrays."); + } + #[cfg(not(feature = "no_object"))] + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for object maps."); + } + if TypeId::of::() == TypeId::of::() + || TypeId::of::() == TypeId::of::<&str>() + || TypeId::of::() == TypeId::of::() + { + panic!("Cannot register indexer for strings."); + } + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for integers."); + } + + self.register_fn(crate::engine::FN_IDX_SET, set_fn) + } + /// Register an index setter for a custom type with the [`Engine`]. + /// + /// Not available under both `no_index` and `no_object`. + /// + /// # Panics + /// + /// Panics if the type is [`Array`], [`Map`], [`String`], + /// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT]. + /// Indexers for arrays, object maps, strings and integers cannot be registered. + /// + /// # Example + /// + /// ``` + /// use rhai::{Engine, Dynamic, EvalAltResult}; + /// + /// #[derive(Clone)] + /// struct TestStruct { + /// fields: Vec + /// } + /// + /// impl TestStruct { + /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } + /// fn set_field(&mut self, index: i64, value: i64) -> Result<(), Box> { + /// self.fields[index as usize] = value; + /// Ok(()) + /// } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// let mut engine = Engine::new(); + /// + /// // Register API for the custom type. + /// # #[cfg(not(feature = "no_object"))] + /// engine.register_type::(); + /// + /// engine + /// .register_fn("new_ts", TestStruct::new) + /// // Register an indexer. + /// .register_indexer_set_result(TestStruct::set_field); + /// + /// # #[cfg(not(feature = "no_index"))] + /// assert_eq!( + /// engine.eval::("let a = new_ts(); a[2] = 42; a")?.fields[2], + /// 42 + /// ); + /// # Ok(()) + /// # } + /// ``` + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] + #[inline] + pub fn register_indexer_set_result< + T: Variant + Clone, + X: Variant + Clone, + V: Variant + Clone, + >( + &mut self, + set_fn: impl Fn(&mut T, X, V) -> Result<(), Box> + SendSync + 'static, + ) -> &mut Self { + #[cfg(not(feature = "no_index"))] + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for arrays."); + } + #[cfg(not(feature = "no_object"))] + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for object maps."); + } + if TypeId::of::() == TypeId::of::() + || TypeId::of::() == TypeId::of::<&str>() + || TypeId::of::() == TypeId::of::() + { + panic!("Cannot register indexer for strings."); + } + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for integers."); + } + + self.register_result_fn(crate::engine::FN_IDX_SET, set_fn) + } + /// Short-hand for registering both index getter and setter functions for a custom type with the [`Engine`]. + /// + /// Not available under both `no_index` and `no_object`. + /// + /// # Panics + /// + /// Panics if the type is [`Array`], [`Map`], [`String`], + /// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT]. + /// Indexers for arrays, object maps, strings and integers cannot be registered. + /// + /// # Example + /// + /// ``` + /// #[derive(Clone)] + /// struct TestStruct { + /// fields: Vec + /// } + /// + /// impl TestStruct { + /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } + /// // Even a getter must start with `&mut self` and not `&self`. + /// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] } + /// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// // Register API for the custom type. + /// # #[cfg(not(feature = "no_object"))] + /// engine.register_type::(); + /// + /// engine + /// .register_fn("new_ts", TestStruct::new) + /// // Register an indexer. + /// .register_indexer_get_set(TestStruct::get_field, TestStruct::set_field); + /// + /// # #[cfg(not(feature = "no_index"))] + /// assert_eq!(engine.eval::("let a = new_ts(); a[2] = 42; a[2]")?, 42); + /// # Ok(()) + /// # } + /// ``` + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] + #[inline(always)] + pub fn register_indexer_get_set( + &mut self, + get_fn: impl Fn(&mut T, X) -> V + SendSync + 'static, + set_fn: impl Fn(&mut T, X, V) + SendSync + 'static, + ) -> &mut Self { + self.register_indexer_get(get_fn) + .register_indexer_set(set_fn) + } + /// Register a shared [`Module`] into the global namespace of [`Engine`]. + /// + /// All functions and type iterators are automatically available to scripts without namespace + /// qualifications. + /// + /// Sub-modules and variables are **ignored**. + /// + /// When searching for functions, modules loaded later are preferred. In other words, loaded + /// modules are searched in reverse order. + #[inline(always)] + pub fn register_global_module(&mut self, module: Shared) -> &mut Self { + // Insert the module into the front. + // The first module is always the global namespace. + self.global_modules.insert(1, module); + self + } + /// Register a shared [`Module`] as a static module namespace with the [`Engine`]. + /// + /// Functions marked [`FnNamespace::Global`] and type iterators are exposed to scripts without + /// namespace qualifications. + /// + /// Not available under `no_module`. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::{Engine, Shared, Module}; + /// + /// let mut engine = Engine::new(); + /// + /// // Create the module + /// let mut module = Module::new(); + /// module.set_native_fn("calc", |x: i64| Ok(x + 1)); + /// + /// let module: Shared = module.into(); + /// + /// engine + /// // Register the module as a fixed sub-module + /// .register_static_module("foo::bar::baz", module.clone()) + /// // Multiple registrations to the same partial path is also OK! + /// .register_static_module("foo::bar::hello", module.clone()) + /// .register_static_module("CalcService", module); + /// + /// assert_eq!(engine.eval::("foo::bar::baz::calc(41)")?, 42); + /// assert_eq!(engine.eval::("foo::bar::hello::calc(41)")?, 42); + /// assert_eq!(engine.eval::("CalcService::calc(41)")?, 42); + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_module"))] + pub fn register_static_module( + &mut self, + name: impl AsRef + Into, + module: Shared, + ) -> &mut Self { + fn register_static_module_raw( + root: &mut std::collections::BTreeMap>, + name: impl AsRef + Into, + module: Shared, + ) { + let separator = crate::tokenizer::Token::DoubleColon.syntax(); + + if !name.as_ref().contains(separator.as_ref()) { + if !module.is_indexed() { + // Index the module (making a clone copy if necessary) if it is not indexed + let mut module = crate::func::native::shared_take_or_clone(module); + module.build_index(); + root.insert(name.into(), module.into()); + } else { + root.insert(name.into(), module); + } + } else { + let mut iter = name.as_ref().splitn(2, separator.as_ref()); + let sub_module = iter.next().expect("contains separator").trim(); + let remainder = iter.next().expect("contains separator").trim(); + + if !root.contains_key(sub_module) { + let mut m = Module::new(); + register_static_module_raw(m.sub_modules_mut(), remainder, module); + m.build_index(); + root.insert(sub_module.into(), m.into()); + } else { + let m = root.remove(sub_module).expect("contains sub-module"); + let mut m = crate::func::native::shared_take_or_clone(m); + register_static_module_raw(m.sub_modules_mut(), remainder, module); + m.build_index(); + root.insert(sub_module.into(), m.into()); + } + } + } + + register_static_module_raw(&mut self.global_sub_modules, name, module); + self + } + /// _(metadata)_ Generate a list of all registered functions. + /// Exported under the `metadata` feature only. + /// + /// Functions from the following sources are included, in order: + /// 1) Functions registered into the global namespace + /// 2) Functions in registered sub-modules + /// 3) Functions in packages (optional) + #[cfg(feature = "metadata")] + #[inline] + #[must_use] + pub fn gen_fn_signatures(&self, include_packages: bool) -> Vec { + let mut signatures = Vec::with_capacity(64); + + signatures.extend(self.global_namespace().gen_fn_signatures()); + + self.global_sub_modules.iter().for_each(|(name, m)| { + signatures.extend(m.gen_fn_signatures().map(|f| format!("{}::{}", name, f))) + }); + + if include_packages { + signatures.extend( + self.global_modules + .iter() + .skip(1) + .flat_map(|m| m.gen_fn_signatures()), + ); + } + + signatures + } +} diff --git a/src/api/run.rs b/src/api/run.rs new file mode 100644 index 00000000..7777b2ca --- /dev/null +++ b/src/api/run.rs @@ -0,0 +1,67 @@ +//! Module that defines the public evaluation API of [`Engine`]. + +use crate::engine::{EvalState, Imports}; +use crate::parser::ParseState; +use crate::{Engine, EvalAltResult, Scope, AST}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +impl Engine { + /// Evaluate a script with own scope, returning any error (if any). + /// + /// ## Constants Propagation + /// + /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated + /// throughout the script _including_ functions. This allows functions to be optimized based on + /// dynamic global constants. + #[inline] + pub fn run_with_scope( + &self, + scope: &mut Scope, + script: &str, + ) -> Result<(), Box> { + let scripts = [script]; + let (stream, tokenizer_control) = + self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); + let mut state = ParseState::new(self, tokenizer_control); + + let ast = self.parse( + &mut stream.peekable(), + &mut state, + scope, + #[cfg(not(feature = "no_optimize"))] + self.optimization_level, + )?; + + self.run_ast_with_scope(scope, &ast) + } + /// Evaluate an AST, returning any error (if any). + #[inline(always)] + pub fn run_ast(&self, ast: &AST) -> Result<(), Box> { + self.run_ast_with_scope(&mut Scope::new(), ast) + } + /// Evaluate an [`AST`] with own scope, returning any error (if any). + #[inline] + pub fn run_ast_with_scope( + &self, + scope: &mut Scope, + ast: &AST, + ) -> Result<(), Box> { + let mods = &mut Imports::new(); + let mut state = EvalState::new(); + if ast.source_raw().is_some() { + mods.source = ast.source_raw().cloned(); + } + #[cfg(not(feature = "no_module"))] + { + mods.embedded_module_resolver = ast.resolver(); + } + + let statements = ast.statements(); + if !statements.is_empty() { + let lib = &[ast.lib()]; + self.eval_global_statements(scope, mods, &mut state, statements, lib, 0)?; + } + Ok(()) + } +} diff --git a/src/api/settings.rs b/src/api/settings.rs deleted file mode 100644 index 8a5682f3..00000000 --- a/src/api/settings.rs +++ /dev/null @@ -1,334 +0,0 @@ -//! Configuration settings for [`Engine`]. - -use crate::tokenizer::Token; -use crate::Engine; -use crate::{engine::Precedence, Identifier}; -#[cfg(feature = "no_std")] -use std::prelude::v1::*; - -#[cfg(not(feature = "unchecked"))] -use std::num::{NonZeroU64, NonZeroUsize}; - -impl Engine { - /// Control whether and how the [`Engine`] will optimize an [`AST`][crate::AST] after compilation. - /// - /// Not available under `no_optimize`. - #[cfg(not(feature = "no_optimize"))] - #[inline(always)] - pub fn set_optimization_level( - &mut self, - optimization_level: crate::OptimizationLevel, - ) -> &mut Self { - self.optimization_level = optimization_level; - self - } - /// The current optimization level. - /// It controls whether and how the [`Engine`] will optimize an [`AST`][crate::AST] after compilation. - /// - /// Not available under `no_optimize`. - #[cfg(not(feature = "no_optimize"))] - #[inline(always)] - #[must_use] - pub const fn optimization_level(&self) -> crate::OptimizationLevel { - self.optimization_level - } - /// Set the maximum levels of function calls allowed for a script in order to avoid - /// infinite recursion and stack overflows. - /// - /// Not available under `unchecked` or `no_function`. - #[cfg(not(feature = "unchecked"))] - #[cfg(not(feature = "no_function"))] - #[inline(always)] - pub fn set_max_call_levels(&mut self, levels: usize) -> &mut Self { - self.limits.max_call_stack_depth = levels; - self - } - /// The maximum levels of function calls allowed for a script. - /// - /// Not available under `unchecked` or `no_function`. - #[cfg(not(feature = "unchecked"))] - #[cfg(not(feature = "no_function"))] - #[inline(always)] - #[must_use] - pub const fn max_call_levels(&self) -> usize { - self.limits.max_call_stack_depth - } - /// Set the maximum number of operations allowed for a script to run to avoid - /// consuming too much resources (0 for unlimited). - /// - /// Not available under `unchecked`. - #[cfg(not(feature = "unchecked"))] - #[inline(always)] - pub fn set_max_operations(&mut self, operations: u64) -> &mut Self { - self.limits.max_operations = NonZeroU64::new(operations); - self - } - /// The maximum number of operations allowed for a script to run (0 for unlimited). - /// - /// Not available under `unchecked`. - #[cfg(not(feature = "unchecked"))] - #[inline] - #[must_use] - pub const fn max_operations(&self) -> u64 { - if let Some(n) = self.limits.max_operations { - n.get() - } else { - 0 - } - } - /// Set the maximum number of imported [modules][crate::Module] allowed for a script. - /// - /// Not available under `unchecked` or `no_module`. - #[cfg(not(feature = "unchecked"))] - #[cfg(not(feature = "no_module"))] - #[inline(always)] - pub fn set_max_modules(&mut self, modules: usize) -> &mut Self { - self.limits.max_modules = modules; - self - } - /// The maximum number of imported [modules][crate::Module] allowed for a script. - /// - /// Not available under `unchecked` or `no_module`. - #[cfg(not(feature = "unchecked"))] - #[cfg(not(feature = "no_module"))] - #[inline(always)] - #[must_use] - pub const fn max_modules(&self) -> usize { - self.limits.max_modules - } - /// Set the depth limits for expressions (0 for unlimited). - /// - /// Not available under `unchecked`. - #[cfg(not(feature = "unchecked"))] - #[inline(always)] - pub fn set_max_expr_depths( - &mut self, - max_expr_depth: usize, - #[cfg(not(feature = "no_function"))] max_function_expr_depth: usize, - ) -> &mut Self { - self.limits.max_expr_depth = NonZeroUsize::new(max_expr_depth); - #[cfg(not(feature = "no_function"))] - { - self.limits.max_function_expr_depth = NonZeroUsize::new(max_function_expr_depth); - } - self - } - /// The depth limit for expressions (0 for unlimited). - /// - /// Not available under `unchecked`. - #[cfg(not(feature = "unchecked"))] - #[inline] - #[must_use] - pub const fn max_expr_depth(&self) -> usize { - if let Some(n) = self.limits.max_expr_depth { - n.get() - } else { - 0 - } - } - /// The depth limit for expressions in functions (0 for unlimited). - /// - /// Not available under `unchecked` or `no_function`. - #[cfg(not(feature = "unchecked"))] - #[cfg(not(feature = "no_function"))] - #[inline] - #[must_use] - pub const fn max_function_expr_depth(&self) -> usize { - if let Some(n) = self.limits.max_function_expr_depth { - n.get() - } else { - 0 - } - } - /// Set the maximum length of [strings][crate::ImmutableString] (0 for unlimited). - /// - /// Not available under `unchecked`. - #[cfg(not(feature = "unchecked"))] - #[inline(always)] - pub fn set_max_string_size(&mut self, max_size: usize) -> &mut Self { - self.limits.max_string_size = NonZeroUsize::new(max_size); - self - } - /// The maximum length of [strings][crate::ImmutableString] (0 for unlimited). - /// - /// Not available under `unchecked`. - #[cfg(not(feature = "unchecked"))] - #[inline] - #[must_use] - pub const fn max_string_size(&self) -> usize { - if let Some(n) = self.limits.max_string_size { - n.get() - } else { - 0 - } - } - /// Set the maximum length of [arrays][crate::Array] (0 for unlimited). - /// - /// Not available under `unchecked` or `no_index`. - #[cfg(not(feature = "unchecked"))] - #[cfg(not(feature = "no_index"))] - #[inline(always)] - pub fn set_max_array_size(&mut self, max_size: usize) -> &mut Self { - self.limits.max_array_size = NonZeroUsize::new(max_size); - self - } - /// The maximum length of [arrays][crate::Array] (0 for unlimited). - /// - /// Not available under `unchecked` or `no_index`. - #[cfg(not(feature = "unchecked"))] - #[cfg(not(feature = "no_index"))] - #[inline] - #[must_use] - pub const fn max_array_size(&self) -> usize { - if let Some(n) = self.limits.max_array_size { - n.get() - } else { - 0 - } - } - /// Set the maximum size of [object maps][crate::Map] (0 for unlimited). - /// - /// Not available under `unchecked` or `no_object`. - #[cfg(not(feature = "unchecked"))] - #[cfg(not(feature = "no_object"))] - #[inline(always)] - pub fn set_max_map_size(&mut self, max_size: usize) -> &mut Self { - self.limits.max_map_size = NonZeroUsize::new(max_size); - self - } - /// The maximum size of [object maps][crate::Map] (0 for unlimited). - /// - /// Not available under `unchecked` or `no_object`. - #[cfg(not(feature = "unchecked"))] - #[cfg(not(feature = "no_object"))] - #[inline] - #[must_use] - pub const fn max_map_size(&self) -> usize { - if let Some(n) = self.limits.max_map_size { - n.get() - } else { - 0 - } - } - /// Set the module resolution service used by the [`Engine`]. - /// - /// Not available under `no_module`. - #[cfg(not(feature = "no_module"))] - #[inline(always)] - pub fn set_module_resolver( - &mut self, - resolver: impl crate::ModuleResolver + 'static, - ) -> &mut Self { - self.module_resolver = Some(Box::new(resolver)); - self - } - /// Disable a particular keyword or operator in the language. - /// - /// # Examples - /// - /// The following will raise an error during parsing because the `if` keyword is disabled - /// and is recognized as a reserved symbol! - /// - /// ```rust,should_panic - /// # fn main() -> Result<(), rhai::ParseError> { - /// use rhai::Engine; - /// - /// let mut engine = Engine::new(); - /// - /// engine.disable_symbol("if"); // disable the 'if' keyword - /// - /// engine.compile("let x = if true { 42 } else { 0 };")?; - /// // ^ 'if' is rejected as a reserved symbol - /// # Ok(()) - /// # } - /// ``` - /// - /// The following will raise an error during parsing because the `+=` operator is disabled. - /// - /// ```rust,should_panic - /// # fn main() -> Result<(), rhai::ParseError> { - /// use rhai::Engine; - /// - /// let mut engine = Engine::new(); - /// - /// engine.disable_symbol("+="); // disable the '+=' operator - /// - /// engine.compile("let x = 42; x += 1;")?; - /// // ^ unknown operator - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn disable_symbol(&mut self, symbol: impl Into) -> &mut Self { - self.disabled_symbols.insert(symbol.into()); - self - } - /// Register a custom operator with a precedence into the language. - /// - /// The operator must be a valid identifier (i.e. it cannot be a symbol). - /// - /// The precedence cannot be zero. - /// - /// # Example - /// - /// ```rust - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let mut engine = Engine::new(); - /// - /// // Register a custom operator called 'foo' and give it - /// // a precedence of 160 (i.e. between +|- and *|/). - /// engine.register_custom_operator("foo", 160).expect("should succeed"); - /// - /// // Register a binary function named 'foo' - /// engine.register_fn("foo", |x: i64, y: i64| (x * y) - (x + y)); - /// - /// assert_eq!( - /// engine.eval_expression::("1 + 2 * 3 foo 4 - 5 / 6")?, - /// 15 - /// ); - /// # Ok(()) - /// # } - /// ``` - pub fn register_custom_operator( - &mut self, - keyword: impl AsRef + Into, - precedence: u8, - ) -> Result<&mut Self, String> { - let precedence = Precedence::new(precedence); - - if precedence.is_none() { - return Err("precedence cannot be zero".into()); - } - - match Token::lookup_from_syntax(keyword.as_ref()) { - // Standard identifiers, reserved keywords and custom keywords are OK - None | Some(Token::Reserved(_)) | Some(Token::Custom(_)) => (), - // Active standard keywords cannot be made custom - // Disabled keywords are OK - Some(token) if token.is_standard_keyword() => { - if !self.disabled_symbols.contains(&*token.syntax()) { - return Err(format!("'{}' is a reserved keyword", keyword.as_ref())); - } - } - // Active standard symbols cannot be made custom - Some(token) if token.is_standard_symbol() => { - if !self.disabled_symbols.contains(&*token.syntax()) { - return Err(format!("'{}' is a reserved operator", keyword.as_ref())); - } - } - // Active standard symbols cannot be made custom - Some(token) if !self.disabled_symbols.contains(&*token.syntax()) => { - return Err(format!("'{}' is a reserved symbol", keyword.as_ref())) - } - // Disabled symbols are OK - Some(_) => (), - } - - // Add to custom keywords - self.custom_keywords.insert(keyword.into(), precedence); - - Ok(self) - } -}