diff --git a/Cargo.toml b/Cargo.toml index f852d289..43339787 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ categories = [ "no-std", "embedded", "parser-implementations" ] num-traits = "*" [features] -#default = ["no_function", "no_index", "no_object", "no_float", "only_i32", "no_stdlib", "unchecked", "no_optimize"] +#default = ["no_function", "no_index", "no_object", "no_float", "only_i32", "no_stdlib", "unchecked", "no_optimize", "sync"] default = [] unchecked = [] # unchecked arithmetic no_stdlib = [] # no standard library of utility functions diff --git a/README.md b/README.md index 86a6482f..dbf37052 100644 --- a/README.md +++ b/README.md @@ -209,12 +209,12 @@ Compiling a script file is also supported: let ast = engine.compile_file("hello_world.rhai".into())?; ``` -Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust - via `call_fn`: +Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust - via `call_fn` +or its cousins `call_fn1` (one argument) and `call_fn0` (no argument). ```rust -// Define a function in a script and load it into the Engine. -// Pass true to 'retain_functions' otherwise these functions will be cleared at the end of consume() -engine.consume(true, +// Define functions in a script. +let ast = engine.compile(true, r" // a function with two parameters: String and i64 fn hello(x, y) { @@ -225,18 +225,26 @@ engine.consume(true, fn hello(x) { x * 2 } + + // this one takes no parameters + fn hello() { + 42 + } ")?; -// Evaluate the function in the AST, passing arguments into the script as a tuple +// Evaluate a function defined in the script, passing arguments into the script as a tuple // if there are more than one. Beware, arguments must be of the correct types because // Rhai does not have built-in type conversions. If arguments of the wrong types are passed, // the Engine will not find the function. -let result: i64 = engine.call_fn("hello", &ast, ( String::from("abc"), 123_i64 ) )?; +let result: i64 = engine.call_fn(&ast, "hello", ( String::from("abc"), 123_i64 ) )?; // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ put arguments in a tuple -let result: i64 = engine.call_fn("hello", 123_i64)? -// ^^^^^^^ calls 'hello' with one parameter (no need for tuple) +let result: i64 = engine.call_fn1(&ast, "hello", 123_i64)? +// ^^^^^^^^ use 'call_fn1' for one argument + +let result: i64 = engine.call_fn0(&ast, "hello")? +// ^^^^^^^^ use 'call_fn0' for no arguments ``` Evaluate expressions only diff --git a/examples/repl.rs b/examples/repl.rs index def1b310..e166b54c 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -77,8 +77,9 @@ fn main() { let mut scope = Scope::new(); let mut input = String::new(); - let mut ast_u: Option = None; - let mut ast: Option = None; + let mut main_ast = AST::new(); + let mut ast_u = AST::new(); + let mut ast = AST::new(); println!("Rhai REPL tool"); println!("=============="); @@ -112,6 +113,10 @@ fn main() { let script = input.trim(); + if script.is_empty() { + continue; + } + // Implement standard commands match script { "help" => { @@ -120,21 +125,13 @@ fn main() { } "exit" | "quit" => break, // quit "astu" => { - if matches!(&ast_u, Some(_)) { - // print the last un-optimized AST - println!("{:#?}", ast_u.as_ref().unwrap()); - } else { - println!("()"); - } + // print the last un-optimized AST + println!("{:#?}", &ast_u); continue; } "ast" => { - if matches!(&ast, Some(_)) { - // print the last AST - println!("{:#?}", ast.as_ref().unwrap()); - } else { - println!("()"); - } + // print the last AST + println!("{:#?}", &ast); continue; } _ => (), @@ -144,12 +141,12 @@ fn main() { .compile_with_scope(&scope, &script) .map_err(EvalAltResult::ErrorParsing) .and_then(|r| { - ast_u = Some(r); + ast_u = r; #[cfg(not(feature = "no_optimize"))] { engine.set_optimization_level(OptimizationLevel::Full); - ast = Some(engine.optimize_ast(&scope, ast_u.as_ref().unwrap())); + ast = engine.optimize_ast(&scope, &ast_u); engine.set_optimization_level(OptimizationLevel::None); } @@ -158,12 +155,21 @@ fn main() { ast = ast_u.clone(); } - engine - .consume_ast_with_scope(&mut scope, true, ast.as_ref().unwrap()) + // Merge the AST into the main + main_ast = main_ast.merge(&ast); + + // Evaluate + let result = engine + .consume_ast_with_scope(&mut scope, &main_ast) .or_else(|err| match err { EvalAltResult::Return(_, _) => Ok(()), err => Err(err), - }) + }); + + // Throw away all the statements, leaving only the functions + main_ast.retain_functions(); + + result }) { println!(); diff --git a/examples/rhai_runner.rs b/examples/rhai_runner.rs index 180f51d6..9a5cfe59 100644 --- a/examples/rhai_runner.rs +++ b/examples/rhai_runner.rs @@ -72,7 +72,7 @@ fn main() { exit(1); } - if let Err(err) = engine.consume(false, &contents) { + if let Err(err) = engine.consume(&contents) { eprintln!("{:=<1$}", "", filename.len()); eprintln!("{}", filename); eprintln!("{:=<1$}", "", filename.len()); diff --git a/src/api.rs b/src/api.rs index 5e140fef..ed14ada3 100644 --- a/src/api.rs +++ b/src/api.rs @@ -2,10 +2,10 @@ use crate::any::{Any, AnyExt, Dynamic}; use crate::call::FuncArgs; -use crate::engine::{make_getter, make_setter, Engine, FnAny, FnSpec, FunctionsLib}; +use crate::engine::{make_getter, make_setter, Engine, FnAny, FnSpec}; use crate::error::ParseError; use crate::fn_register::RegisterFn; -use crate::parser::{lex, parse, parse_global_expr, FnDef, Position, AST}; +use crate::parser::{lex, parse, parse_global_expr, Position, AST}; use crate::result::EvalAltResult; use crate::scope::Scope; @@ -17,7 +17,6 @@ use crate::stdlib::{ boxed::Box, collections::HashMap, string::{String, ToString}, - sync::Arc, vec::Vec, }; #[cfg(not(feature = "no_std"))] @@ -746,7 +745,7 @@ impl<'e> Engine<'e> { scope: &mut Scope, ast: &AST, ) -> Result { - self.eval_ast_with_scope_raw(scope, false, ast)? + self.eval_ast_with_scope_raw(scope, ast)? .try_cast::() .map_err(|a| { EvalAltResult::ErrorMismatchOutputType( @@ -759,16 +758,11 @@ impl<'e> Engine<'e> { pub(crate) fn eval_ast_with_scope_raw( &mut self, scope: &mut Scope, - retain_functions: bool, ast: &AST, ) -> Result { - if !retain_functions { - self.clear_functions(); - } - let statements = { let AST(statements, functions) = ast; - self.load_script_functions(functions); + self.fn_lib = Some(functions.clone()); statements }; @@ -776,9 +770,7 @@ impl<'e> Engine<'e> { .iter() .try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0)); - if !retain_functions { - self.clear_functions(); - } + self.fn_lib = None; result.or_else(|err| match err { EvalAltResult::Return(out, _) => Ok(out), @@ -788,52 +780,33 @@ impl<'e> Engine<'e> { /// Evaluate a file, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ - /// and not cleared from run to run. #[cfg(not(feature = "no_std"))] - pub fn consume_file( - &mut self, - retain_functions: bool, - path: PathBuf, - ) -> Result<(), EvalAltResult> { - Self::read_file(path).and_then(|contents| self.consume(retain_functions, &contents)) + pub fn consume_file(&mut self, path: PathBuf) -> Result<(), EvalAltResult> { + Self::read_file(path).and_then(|contents| self.consume(&contents)) } /// Evaluate a file with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ - /// and not cleared from run to run. #[cfg(not(feature = "no_std"))] pub fn consume_file_with_scope( &mut self, scope: &mut Scope, - retain_functions: bool, path: PathBuf, ) -> Result<(), EvalAltResult> { - Self::read_file(path) - .and_then(|contents| self.consume_with_scope(scope, retain_functions, &contents)) + Self::read_file(path).and_then(|contents| self.consume_with_scope(scope, &contents)) } /// Evaluate a string, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ - /// and not cleared from run to run. - pub fn consume(&mut self, retain_functions: bool, input: &str) -> Result<(), EvalAltResult> { - self.consume_with_scope(&mut Scope::new(), retain_functions, input) + pub fn consume(&mut self, input: &str) -> Result<(), EvalAltResult> { + self.consume_with_scope(&mut Scope::new(), input) } /// Evaluate a string with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ - /// and not cleared from run to run. pub fn consume_with_scope( &mut self, scope: &mut Scope, - retain_functions: bool, input: &str, ) -> Result<(), EvalAltResult> { let tokens_stream = lex(input); @@ -841,36 +814,25 @@ impl<'e> Engine<'e> { let ast = parse(&mut tokens_stream.peekable(), self, scope) .map_err(EvalAltResult::ErrorParsing)?; - self.consume_ast_with_scope(scope, retain_functions, &ast) + self.consume_ast_with_scope(scope, &ast) } /// Evaluate an AST, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ - /// and not cleared from run to run. - pub fn consume_ast(&mut self, retain_functions: bool, ast: &AST) -> Result<(), EvalAltResult> { - self.consume_ast_with_scope(&mut Scope::new(), retain_functions, ast) + pub fn consume_ast(&mut self, ast: &AST) -> Result<(), EvalAltResult> { + self.consume_ast_with_scope(&mut Scope::new(), ast) } /// Evaluate an `AST` with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ - /// and not cleared from run to run. pub fn consume_ast_with_scope( &mut self, scope: &mut Scope, - retain_functions: bool, ast: &AST, ) -> Result<(), EvalAltResult> { - if !retain_functions { - self.clear_functions(); - } - let statements = { let AST(ref statements, ref functions) = ast; - self.load_script_functions(functions); + self.fn_lib = Some(functions.clone()); statements }; @@ -878,9 +840,7 @@ impl<'e> Engine<'e> { .iter() .try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0)); - if !retain_functions { - self.clear_functions(); - } + self.fn_lib = None; result.map(|_| ()).or_else(|err| match err { EvalAltResult::Return(_, _) => Ok(()), @@ -888,21 +848,7 @@ impl<'e> Engine<'e> { }) } - /// Load a list of functions into the Engine. - pub(crate) fn load_script_functions<'a>( - &mut self, - functions: impl IntoIterator>, - ) { - if self.fn_lib.is_none() { - self.fn_lib = Some(FunctionsLib::new()); - } - - functions.into_iter().cloned().for_each(|f| { - self.fn_lib.as_mut().unwrap().add_or_replace_function(f); - }); - } - - /// Call a script function retained inside the Engine. + /// Call a script function defined in an `AST` with no argument. /// /// # Example /// @@ -915,11 +861,71 @@ impl<'e> Engine<'e> { /// /// let mut engine = Engine::new(); /// - /// // Set 'retain_functions' in 'consume' to keep the function definitions - /// engine.consume(true, "fn add(x, y) { len(x) + y }")?; + /// let ast = engine.compile("fn num() { 42 }")?; /// /// // Call the script-defined function - /// let result: i64 = engine.call_fn("add", (String::from("abc"), 123_i64))?; + /// let result: i64 = engine.call_fn0(&ast, "num")?; + /// + /// assert_eq!(result, 42); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_function"))] + pub fn call_fn0(&mut self, ast: &AST, name: &str) -> Result { + self.call_fn_internal(ast, name, vec![]) + } + + /// Call a script function defined in an `AST` with one argument. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), rhai::EvalAltResult> { + /// # #[cfg(not(feature = "no_stdlib"))] + /// # #[cfg(not(feature = "no_function"))] + /// # { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// let ast = engine.compile("fn inc(x) { x + 1 }")?; + /// + /// // Call the script-defined function + /// let result: i64 = engine.call_fn1(&ast, "inc", 123_i64)?; + /// + /// assert_eq!(result, 124); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_function"))] + pub fn call_fn1( + &mut self, + ast: &AST, + name: &str, + arg: A, + ) -> Result { + self.call_fn_internal(ast, name, vec![arg.into_dynamic()]) + } + + /// Call a script function defined in an `AST` with multiple arguments. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), rhai::EvalAltResult> { + /// # #[cfg(not(feature = "no_stdlib"))] + /// # #[cfg(not(feature = "no_function"))] + /// # { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// let ast = engine.compile("fn add(x, y) { len(x) + y }")?; + /// + /// // Call the script-defined function + /// let result: i64 = engine.call_fn(&ast, "add", (String::from("abc"), 123_i64))?; /// /// assert_eq!(result, 126); /// # } @@ -929,20 +935,37 @@ impl<'e> Engine<'e> { #[cfg(not(feature = "no_function"))] pub fn call_fn( &mut self, + ast: &AST, name: &str, args: A, ) -> Result { - let mut values = args.into_vec(); + self.call_fn_internal(ast, name, args.into_vec()) + } + + #[cfg(not(feature = "no_function"))] + fn call_fn_internal( + &mut self, + ast: &AST, + name: &str, + mut values: Vec, + ) -> Result { let mut arg_values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect(); - self.call_fn_raw(name, &mut arg_values, None, Position::none(), 0)? + self.fn_lib = Some(ast.1.clone()); + + let result = self + .call_fn_raw(name, &mut arg_values, None, Position::none(), 0)? .try_cast() .map_err(|a| { EvalAltResult::ErrorMismatchOutputType( self.map_type_name((*a).type_name()).into(), Position::none(), ) - }) + }); + + self.fn_lib = None; + + result } /// Optimize the `AST` with constants defined in an external Scope. @@ -958,10 +981,12 @@ impl<'e> Engine<'e> { /// (i.e. with `scope.push_constant(...)`). Then, the `AST is cloned and the copy re-optimized before running. #[cfg(not(feature = "no_optimize"))] pub fn optimize_ast(&self, scope: &Scope, ast: &AST) -> AST { - let statements = ast.0.clone(); - let functions = ast.1.iter().map(|f| (**f).clone()).collect(); - - optimize_into_ast(self, scope, statements, functions) + optimize_into_ast( + self, + scope, + ast.0.clone(), + ast.1.iter().map(|fn_def| fn_def.as_ref().clone()).collect(), + ) } /// Override default action of `print` (print to stdout using `println!`) @@ -978,7 +1003,7 @@ impl<'e> Engine<'e> { /// /// // Override action of 'print' function /// engine.on_print(|s| result.push_str(s)); - /// engine.consume(false, "print(40 + 2);")?; + /// engine.consume("print(40 + 2);")?; /// } /// assert_eq!(result, "42"); /// # Ok(()) @@ -1002,7 +1027,7 @@ impl<'e> Engine<'e> { /// /// // Override action of 'print' function /// engine.on_print(|s| result.push_str(s)); - /// engine.consume(false, "print(40 + 2);")?; + /// engine.consume("print(40 + 2);")?; /// } /// assert_eq!(result, "42"); /// # Ok(()) @@ -1027,7 +1052,7 @@ impl<'e> Engine<'e> { /// /// // Override action of 'debug' function /// engine.on_debug(|s| result.push_str(s)); - /// engine.consume(false, r#"debug("hello");"#)?; + /// engine.consume(r#"debug("hello");"#)?; /// } /// assert_eq!(result, "\"hello\""); /// # Ok(()) @@ -1051,7 +1076,7 @@ impl<'e> Engine<'e> { /// /// // Override action of 'debug' function /// engine.on_debug(|s| result.push_str(s)); - /// engine.consume(false, r#"debug("hello");"#)?; + /// engine.consume(r#"debug("hello");"#)?; /// } /// assert_eq!(result, "\"hello\""); /// # Ok(()) diff --git a/src/call.rs b/src/call.rs index c1147a53..ac33e2c7 100644 --- a/src/call.rs +++ b/src/call.rs @@ -3,10 +3,6 @@ #![allow(non_snake_case)] use crate::any::{Any, Dynamic}; -use crate::parser::INT; - -#[cfg(not(feature = "no_index"))] -use crate::engine::Array; use crate::stdlib::{string::String, vec, vec::Vec}; @@ -18,36 +14,7 @@ pub trait FuncArgs { fn into_vec(self) -> Vec; } -/// Macro to implement `FuncArgs` for a single standard type that can be converted -/// into `Dynamic`. -macro_rules! impl_std_args { - ($($p:ty),*) => { - $( - impl FuncArgs for $p { - fn into_vec(self) -> Vec { - vec![self.into_dynamic()] - } - } - )* - }; -} - -impl_std_args!(String, char, bool); - -#[cfg(not(feature = "no_index"))] -impl_std_args!(Array); - -#[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "only_i64"))] -impl_std_args!(u8, i8, u16, i16, u32, i32, u64, i64); - -#[cfg(any(feature = "only_i32", feature = "only_i64"))] -impl_std_args!(INT); - -#[cfg(not(feature = "no_float"))] -impl_std_args!(f32, f64); - -/// Macro to implement `FuncArgs` for tuples of standard types (each can be +// Macro to implement `FuncArgs` for tuples of standard types (each can be /// converted into `Dynamic`). macro_rules! impl_args { ($($p:ident),*) => { diff --git a/src/engine.rs b/src/engine.rs index b4bc949d..e1ba5401 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,7 +1,7 @@ //! Main module defining the script evaluation `Engine`. use crate::any::{Any, AnyExt, Dynamic, Variant}; -use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt, INT}; +use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt, AST, INT}; use crate::result::EvalAltResult; use crate::scope::{EntryRef as ScopeSource, EntryType as ScopeEntryType, Scope}; @@ -16,6 +16,8 @@ use crate::stdlib::{ collections::HashMap, format, iter::once, + ops::{Deref, DerefMut}, + rc::Rc, string::{String, ToString}, sync::Arc, vec, @@ -130,8 +132,11 @@ pub struct FnSpec<'a> { /// to search for it. /// /// So instead this is implemented as a sorted list and binary searched. -#[derive(Debug)] -pub struct FunctionsLib(Vec>); +#[derive(Debug, Clone)] +pub struct FunctionsLib( + #[cfg(feature = "sync")] Vec>, + #[cfg(not(feature = "sync"))] Vec>, +); impl FnDef { /// Function to order two FnDef records, for binary search. @@ -150,28 +155,75 @@ impl FunctionsLib { pub fn new() -> Self { FunctionsLib(Vec::new()) } + /// Create a new `FunctionsLib` from a collection of `FnDef`. + pub fn from_vec(vec: Vec) -> Self { + #[cfg(feature = "sync")] + { + FunctionsLib(vec.into_iter().map(Arc::new).collect()) + } + #[cfg(not(feature = "sync"))] + { + FunctionsLib(vec.into_iter().map(Rc::new).collect()) + } + } /// Does a certain function exist in the `FunctionsLib`? pub fn has_function(&self, name: &str, params: usize) -> bool { self.0.binary_search_by(|f| f.compare(name, params)).is_ok() } - /// Add a function (or replace an existing one) in the `FunctionsLib`. - pub fn add_or_replace_function(&mut self, fn_def: Arc) { - match self - .0 - .binary_search_by(|f| f.compare(&fn_def.name, fn_def.params.len())) - { - Ok(n) => self.0[n] = fn_def, - Err(n) => self.0.insert(n, fn_def), - } - } /// Get a function definition from the `FunctionsLib`. - pub fn get_function(&self, name: &str, params: usize) -> Option> { + pub fn get_function(&self, name: &str, params: usize) -> Option<&FnDef> { if let Ok(n) = self.0.binary_search_by(|f| f.compare(name, params)) { - Some(self.0[n].clone()) + Some(&self.0[n]) } else { None } } + /// Merge another `FunctionsLib` into this `FunctionsLib`. + pub fn merge(&self, other: &Self) -> Self { + if self.is_empty() { + other.clone() + } else if other.is_empty() { + self.clone() + } else { + let mut functions = self.clone(); + + other.iter().cloned().for_each(|fn_def| { + if let Some((n, _)) = functions + .iter() + .enumerate() + .find(|(_, f)| f.name == fn_def.name && f.params.len() == fn_def.params.len()) + { + functions[n] = fn_def; + } else { + functions.push(fn_def); + } + }); + + functions + } + } +} + +impl Deref for FunctionsLib { + #[cfg(feature = "sync")] + type Target = Vec>; + #[cfg(not(feature = "sync"))] + type Target = Vec>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for FunctionsLib { + #[cfg(feature = "sync")] + fn deref_mut(&mut self) -> &mut Vec> { + &mut self.0 + } + #[cfg(not(feature = "sync"))] + fn deref_mut(&mut self) -> &mut Vec> { + &mut self.0 + } } /// Rhai main scripting engine. @@ -193,8 +245,14 @@ impl FunctionsLib { pub struct Engine<'e> { /// A hashmap containing all compiled functions known to the engine. pub(crate) functions: Option, Box>>, + /// A hashmap containing all script-defined functions. - pub(crate) fn_lib: Option, + #[cfg(feature = "sync")] + pub(crate) fn_lib: Option>, + /// A hashmap containing all script-defined functions. + #[cfg(not(feature = "sync"))] + pub(crate) fn_lib: Option>, + /// A hashmap containing all iterators known to the engine. pub(crate) type_iterators: Option>>, /// A hashmap mapping type names to pretty-print names. @@ -203,12 +261,14 @@ pub struct Engine<'e> { /// Closure for implementing the `print` command. #[cfg(feature = "sync")] pub(crate) on_print: Option>, + /// Closure for implementing the `print` command. #[cfg(not(feature = "sync"))] pub(crate) on_print: Option>, /// Closure for implementing the `debug` command. #[cfg(feature = "sync")] pub(crate) on_debug: Option>, + /// Closure for implementing the `debug` command. #[cfg(not(feature = "sync"))] pub(crate) on_debug: Option>, @@ -382,8 +442,8 @@ impl Engine<'_> { level: usize, ) -> Result { // First search in script-defined functions (can override built-in) - if let Some(ref fn_lib) = self.fn_lib { - if let Some(fn_def) = fn_lib.get_function(fn_name, args.len()) { + if let Some(ref fn_lib_arc) = self.fn_lib { + if let Some(fn_def) = fn_lib_arc.clone().get_function(fn_name, args.len()) { let mut scope = Scope::new(); scope.extend( @@ -1276,6 +1336,7 @@ impl Engine<'_> { let pos = args_expr_list[0].position(); let r = self.eval_expr(scope, &args_expr_list[0], level)?; + // Get the script text by evaluating the expression let script = r.downcast_ref::() .map(String::as_str) @@ -1286,8 +1347,9 @@ impl Engine<'_> { ) })?; + // Compile the script text #[cfg(not(feature = "no_optimize"))] - let ast = { + let mut ast = { let orig_optimization_level = self.optimization_level; self.set_optimization_level(OptimizationLevel::None); @@ -1298,11 +1360,38 @@ impl Engine<'_> { }; #[cfg(feature = "no_optimize")] - let ast = self.compile(script).map_err(EvalAltResult::ErrorParsing)?; + let mut ast = self.compile(script).map_err(EvalAltResult::ErrorParsing)?; - Ok(self - .eval_ast_with_scope_raw(scope, true, &ast) - .map_err(|err| err.set_position(pos))?) + // If new functions are defined, merge it into the current functions library + let merged = AST( + ast.0, + if let Some(ref fn_lib) = self.fn_lib { + #[cfg(feature = "sync")] + { + Arc::new(fn_lib.as_ref().merge(&ast.1)) + } + #[cfg(not(feature = "sync"))] + { + Rc::new(fn_lib.as_ref().merge(&ast.1)) + } + } else { + ast.1 + }, + ); + + // Evaluate the AST + let result = self + .eval_ast_with_scope_raw(scope, &merged) + .map_err(|err| err.set_position(pos)); + + // Update the new functions library if there are new functions + self.fn_lib = if !merged.1.is_empty() { + Some(merged.1) + } else { + None + }; + + Ok(result?) } // Normal function call diff --git a/src/optimize.rs b/src/optimize.rs index 9f9f101b..5fa2769b 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -2,13 +2,15 @@ use crate::any::{Any, Dynamic}; use crate::engine::{ - Engine, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF, + Engine, FunctionsLib, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_EVAL, KEYWORD_PRINT, + KEYWORD_TYPE_OF, }; use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST}; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; use crate::stdlib::{ boxed::Box, + rc::Rc, string::{String, ToString}, sync::Arc, vec, @@ -451,8 +453,8 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { && args.iter().all(|expr| expr.is_constant()) // all arguments are constants => { // First search in script-defined functions (can override built-in) - if let Some(ref fn_lib) = state.engine.fn_lib { - if fn_lib.has_function(&id, args.len()) { + if let Some(ref fn_lib_arc) = state.engine.fn_lib { + if fn_lib_arc.has_function(&id, args.len()) { // A script-defined function overrides the built-in function - do not make the call return Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos); } @@ -584,15 +586,10 @@ pub fn optimize_into_ast( statements: Vec, functions: Vec, ) -> AST { - AST( - match engine.optimization_level { - OptimizationLevel::None => statements, - OptimizationLevel::Simple | OptimizationLevel::Full => { - optimize(statements, engine, &scope) - } - }, + let fn_lib = FunctionsLib::from_vec( functions - .into_iter() + .iter() + .cloned() .map(|mut fn_def| { if engine.optimization_level != OptimizationLevel::None { let pos = fn_def.body.position(); @@ -612,9 +609,21 @@ pub fn optimize_into_ast( stmt => stmt, }; } - - Arc::new(fn_def) + fn_def }) .collect(), + ); + + AST( + match engine.optimization_level { + OptimizationLevel::None => statements, + OptimizationLevel::Simple | OptimizationLevel::Full => { + optimize(statements, engine, &scope) + } + }, + #[cfg(feature = "sync")] + Arc::new(fn_lib), + #[cfg(not(feature = "sync"))] + Rc::new(fn_lib), ) } diff --git a/src/parser.rs b/src/parser.rs index 0ec77e86..d125878b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,7 +1,7 @@ //! Main module defining the lexer and parser. use crate::any::{Any, AnyExt, Dynamic}; -use crate::engine::Engine; +use crate::engine::{Engine, FunctionsLib}; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::scope::{EntryType as ScopeEntryType, Scope}; @@ -16,6 +16,7 @@ use crate::stdlib::{ fmt, format, iter::Peekable, ops::Add, + rc::Rc, str::Chars, str::FromStr, string::{String, ToString}, @@ -165,10 +166,19 @@ impl fmt::Debug for Position { /// /// Currently, `AST` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. #[derive(Debug, Clone)] -pub struct AST(pub(crate) Vec, pub(crate) Vec>); +pub struct AST( + pub(crate) Vec, + #[cfg(feature = "sync")] pub(crate) Arc, + #[cfg(not(feature = "sync"))] pub(crate) Rc, +); impl AST { - /// Merge two `AST` into one. Both `AST`'s are consumed and a new, merged, version + /// Create a new `AST`. + pub fn new() -> Self { + Default::default() + } + + /// Merge two `AST` into one. Both `AST`'s are untouched and a new, merged, version /// is returned. /// /// The second `AST` is simply appended to the end of the first _without any processing_. @@ -192,10 +202,10 @@ impl AST { /// let ast1 = engine.compile(r#"fn foo(x) { 42 + x } foo(1)"#)?; /// let ast2 = engine.compile(r#"fn foo(n) { "hello" + n } foo("!")"#)?; /// - /// let ast = ast1.merge(ast2); // Merge 'ast2' into 'ast1' + /// let ast = ast1.merge(&ast2); // Merge 'ast2' into 'ast1' /// /// // Notice that using the '+' operator also works: - /// // let ast = ast1 + ast2; + /// // let ast = &ast1 + &ast2; /// /// // 'ast' is essentially: /// // @@ -210,29 +220,62 @@ impl AST { /// # Ok(()) /// # } /// ``` - pub fn merge(self, mut other: Self) -> Self { - let Self(mut ast, mut functions) = self; + pub fn merge(&self, other: &Self) -> Self { + let Self(ast, functions) = self; - ast.append(&mut other.0); - - for fn_def in other.1 { - if let Some((n, _)) = functions - .iter() - .enumerate() - .find(|(_, f)| f.name == fn_def.name && f.params.len() == fn_def.params.len()) - { - functions[n] = fn_def; - } else { - functions.push(fn_def); + let ast = match (ast.is_empty(), other.0.is_empty()) { + (false, false) => { + let mut ast = ast.clone(); + ast.extend(other.0.iter().cloned()); + ast } - } + (false, true) => ast.clone(), + (true, false) => other.0.clone(), + (true, true) => vec![], + }; - Self(ast, functions) + #[cfg(feature = "sync")] + { + Self(ast, Arc::new(functions.merge(other.1.as_ref()))) + } + #[cfg(not(feature = "sync"))] + { + Self(ast, Rc::new(functions.merge(other.1.as_ref()))) + } + } + + /// Clear all function definitions in the `AST`. + pub fn clear_functions(&mut self) { + #[cfg(feature = "sync")] + { + self.1 = Arc::new(FunctionsLib::new()); + } + #[cfg(not(feature = "sync"))] + { + self.1 = Rc::new(FunctionsLib::new()); + } + } + + /// Clear all statements in the `AST`, leaving only function definitions. + pub fn retain_functions(&mut self) { + self.0 = vec![]; } } -impl Add for AST { - type Output = Self; +impl Default for AST { + fn default() -> Self { + #[cfg(feature = "sync")] + { + Self(vec![], Arc::new(FunctionsLib::new())) + } + #[cfg(not(feature = "sync"))] + { + Self(vec![], Rc::new(FunctionsLib::new())) + } + } +} +impl Add for &AST { + type Output = AST; fn add(self, rhs: Self) -> Self::Output { self.merge(rhs) @@ -2592,7 +2635,17 @@ pub fn parse_global_expr<'a, 'e>( // // Do not optimize AST if `no_optimize` #[cfg(feature = "no_optimize")] - AST(vec![Stmt::Expr(Box::new(expr))], vec![]), + AST( + vec![Stmt::Expr(Box::new(expr))], + #[cfg(feature = "sync")] + { + Arc::new(FunctionsLib::new()) + }, + #[cfg(not(feature = "sync"))] + { + Rc::new(FunctionsLib::new()) + }, + ), ) } @@ -2667,7 +2720,7 @@ pub fn parse<'a, 'e>( // // Do not optimize AST if `no_optimize` #[cfg(feature = "no_optimize")] - AST(statements, functions.into_iter().map(Arc::new).collect()), + AST(statements, Arc::new(FunctionsLib::from_vec(functions))), ) } diff --git a/tests/call_fn.rs b/tests/call_fn.rs index c5b8b307..555219f2 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -22,8 +22,7 @@ fn test_fn() -> Result<(), EvalAltResult> { fn test_call_fn() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - engine.consume( - true, + let ast = engine.compile( r" fn hello(x, y) { x + y @@ -31,14 +30,20 @@ fn test_call_fn() -> Result<(), EvalAltResult> { fn hello(x) { x * 2 } - ", + fn hello() { + 42 + } + ", )?; - let r: i64 = engine.call_fn("hello", (42 as INT, 123 as INT))?; + let r: i64 = engine.call_fn(&ast, "hello", (42 as INT, 123 as INT))?; assert_eq!(r, 165); - let r: i64 = engine.call_fn("hello", 123 as INT)?; + let r: i64 = engine.call_fn1(&ast, "hello", 123 as INT)?; assert_eq!(r, 246); + let r: i64 = engine.call_fn0(&ast, "hello")?; + assert_eq!(r, 42); + Ok(()) }