diff --git a/src/api.rs b/src/api.rs index 5a683d4a..6c7a6e7e 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,7 +1,9 @@ //! Module that defines the extern API of `Engine`. use crate::any::{Dynamic, Variant}; -use crate::engine::{make_getter, make_setter, Engine, State, FUNC_INDEXER}; +use crate::engine::{ + get_script_function_by_signature, make_getter, make_setter, Engine, State, FUNC_INDEXER, +}; use crate::error::ParseError; use crate::fn_call::FuncArgs; use crate::fn_native::{IteratorFn, SendSync}; @@ -1077,10 +1079,7 @@ impl Engine { arg_values: &mut [Dynamic], ) -> Result> { let mut args: StaticVec<_> = arg_values.iter_mut().collect(); - let lib = ast.lib(); - - let fn_def = lib - .get_function_by_signature(name, args.len(), true) + let fn_def = get_script_function_by_signature(ast.lib(), name, args.len(), true) .ok_or_else(|| { Box::new(EvalAltResult::ErrorFunctionNotFound( name.into(), @@ -1091,7 +1090,7 @@ impl Engine { let mut state = State::new(); let args = args.as_mut(); - self.call_script_fn(scope, &mut state, &lib, name, fn_def, args, 0) + self.call_script_fn(scope, &mut state, ast.lib(), name, fn_def, args, 0) } /// Optimize the `AST` with constants defined in an external Scope. @@ -1114,8 +1113,9 @@ impl Engine { ) -> AST { let lib = ast .lib() - .iter() - .map(|(_, fn_def)| fn_def.as_ref().clone()) + .iter_fn() + .filter(|(_, _, _, f)| f.is_script()) + .map(|(_, _, _, f)| f.get_fn_def().clone()) .collect(); let stmt = mem::take(ast.statements_mut()); diff --git a/src/engine.rs b/src/engine.rs index a5001df3..b79c497b 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -3,16 +3,16 @@ use crate::any::{Dynamic, Union}; use crate::calc_fn_hash; use crate::error::ParseErrorType; -use crate::fn_native::{CallableFunction, Callback, FnCallArgs, Shared}; +use crate::fn_native::{CallableFunction, Callback, FnCallArgs}; use crate::module::{resolvers, Module, ModuleResolver}; use crate::optimize::OptimizationLevel; use crate::packages::{CorePackage, Package, PackageLibrary, PackagesCollection, StandardPackage}; -use crate::parser::{Expr, FnAccess, FnDef, ImmutableString, ReturnType, Stmt, AST, INT}; +use crate::parser::{Expr, FnAccess, ImmutableString, ReturnType, ScriptFnDef, Stmt, AST, INT}; use crate::r#unsafe::{unsafe_cast_var_name_to_lifetime, unsafe_mut_cast_to_lifetime}; use crate::result::EvalAltResult; use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::token::Position; -use crate::utils::{StaticVec, StraightHasherBuilder}; +use crate::utils::StaticVec; #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; @@ -24,7 +24,6 @@ use crate::stdlib::{ format, iter::{empty, once}, mem, - ops::{Deref, DerefMut}, string::{String, ToString}, vec::Vec, }; @@ -189,88 +188,24 @@ impl State { } } -/// A type that holds a library (`HashMap`) of script-defined functions. -/// -/// Since script-defined functions have `Dynamic` parameters, functions with the same name -/// and number of parameters are considered equivalent. -/// -/// The key of the `HashMap` is a `u64` hash calculated by the function `calc_fn_hash`. -#[derive(Debug, Clone, Default)] -pub struct FunctionsLib(HashMap, StraightHasherBuilder>); +/// Get a script-defined function definition from a module. +pub fn get_script_function_by_signature<'a>( + module: &'a Module, + name: &str, + params: usize, + public_only: bool, +) -> Option<&'a ScriptFnDef> { + // Qualifiers (none) + function name + number of arguments. + let hash_fn_def = calc_fn_hash(empty(), name, params, empty()); + let func = module.get_fn(hash_fn_def)?; + if !func.is_script() { + return None; + } + let fn_def = func.get_fn_def(); -impl FunctionsLib { - /// Create a new `FunctionsLib` from a collection of `FnDef`. - pub fn from_iter(vec: impl IntoIterator) -> Self { - FunctionsLib( - vec.into_iter() - .map(|fn_def| { - // Qualifiers (none) + function name + number of arguments. - let hash = calc_fn_hash(empty(), &fn_def.name, fn_def.params.len(), empty()); - (hash, fn_def.into()) - }) - .collect(), - ) - } - /// Does a certain function exist in the `FunctionsLib`? - /// - /// The `u64` hash is calculated by the function `crate::calc_fn_hash`. - pub fn has_function(&self, hash_fn_def: u64) -> bool { - self.contains_key(&hash_fn_def) - } - /// Get a function definition from the `FunctionsLib`. - /// - /// The `u64` hash is calculated by the function `crate::calc_fn_hash`. - pub fn get_function(&self, hash_fn_def: u64) -> Option<&FnDef> { - self.get(&hash_fn_def).map(|fn_def| fn_def.as_ref()) - } - /// Get a function definition from the `FunctionsLib`. - pub fn get_function_by_signature( - &self, - name: &str, - params: usize, - public_only: bool, - ) -> Option<&FnDef> { - // Qualifiers (none) + function name + number of arguments. - let hash_fn_def = calc_fn_hash(empty(), name, params, empty()); - let fn_def = self.get_function(hash_fn_def); - - match fn_def.as_ref().map(|f| f.access) { - None => None, - Some(FnAccess::Private) if public_only => None, - Some(FnAccess::Private) | Some(FnAccess::Public) => fn_def, - } - } - /// 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(); - functions.extend(other.iter().map(|(hash, fn_def)| (*hash, fn_def.clone()))); - functions - } - } -} - -impl From)>> for FunctionsLib { - fn from(values: Vec<(u64, Shared)>) -> Self { - FunctionsLib(values.into_iter().collect()) - } -} - -impl Deref for FunctionsLib { - type Target = HashMap, StraightHasherBuilder>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for FunctionsLib { - fn deref_mut(&mut self) -> &mut HashMap, StraightHasherBuilder> { - &mut self.0 + match fn_def.access { + FnAccess::Private if public_only => None, + FnAccess::Private | FnAccess::Public => Some(&fn_def), } } @@ -575,7 +510,7 @@ impl Engine { &self, scope: &mut Scope, state: &mut State, - lib: &FunctionsLib, + lib: &Module, fn_name: &str, hashes: (u64, u64), args: &mut FnCallArgs, @@ -634,12 +569,27 @@ impl Engine { } } + // Search for the function // First search in script-defined functions (can override built-in) - if !native_only { - if let Some(fn_def) = lib.get(&hashes.1) { + // Then search registered native functions (can override packages) + // Then search packages + // NOTE: We skip script functions for global_module and packages, and native functions for lib + let func = if !native_only { + lib.get_fn(hashes.1) //.or_else(|| lib.get_fn(hashes.0)) + } else { + None + } + //.or_else(|| self.global_module.get_fn(hashes.1)) + .or_else(|| self.global_module.get_fn(hashes.0)) + //.or_else(|| self.packages.get_fn(hashes.1)) + .or_else(|| self.packages.get_fn(hashes.0)); + + if let Some(func) = func { + if func.is_script() { + // Run scripted function normalize_first_arg(is_ref, &mut this_copy, &mut old_this_ptr, args); - // Run scripted function + let fn_def = func.get_fn_def(); let result = self.call_script_fn(scope, state, lib, fn_name, fn_def, args, level)?; @@ -647,55 +597,48 @@ impl Engine { restore_first_arg(old_this_ptr, args); return Ok((result, false)); + } else { + // Calling pure function in method-call? + normalize_first_arg( + func.is_pure() && is_ref, + &mut this_copy, + &mut old_this_ptr, + args, + ); + + // Run external function + let result = func.get_native_fn()(args)?; + + // Restore the original reference + restore_first_arg(old_this_ptr, args); + + // See if the function match print/debug (which requires special processing) + return Ok(match fn_name { + KEYWORD_PRINT => ( + (self.print)(result.as_str().map_err(|type_name| { + Box::new(EvalAltResult::ErrorMismatchOutputType( + type_name.into(), + Position::none(), + )) + })?) + .into(), + false, + ), + KEYWORD_DEBUG => ( + (self.debug)(result.as_str().map_err(|type_name| { + Box::new(EvalAltResult::ErrorMismatchOutputType( + type_name.into(), + Position::none(), + )) + })?) + .into(), + false, + ), + _ => (result, func.is_method()), + }); } } - // Search built-in's and external functions - if let Some(func) = self - .global_module - .get_fn(hashes.0) - .or_else(|| self.packages.get_fn(hashes.0)) - { - // Calling pure function in method-call? - normalize_first_arg( - func.is_pure() && is_ref, - &mut this_copy, - &mut old_this_ptr, - args, - ); - - // Run external function - let result = func.get_native_fn()(args)?; - - // Restore the original reference - restore_first_arg(old_this_ptr, args); - - // See if the function match print/debug (which requires special processing) - return Ok(match fn_name { - KEYWORD_PRINT => ( - (self.print)(result.as_str().map_err(|type_name| { - Box::new(EvalAltResult::ErrorMismatchOutputType( - type_name.into(), - Position::none(), - )) - })?) - .into(), - false, - ), - KEYWORD_DEBUG => ( - (self.debug)(result.as_str().map_err(|type_name| { - Box::new(EvalAltResult::ErrorMismatchOutputType( - type_name.into(), - Position::none(), - )) - })?) - .into(), - false, - ), - _ => (result, func.is_method()), - }); - } - // See if it is built in. if args.len() == 2 { match run_builtin_binary_op(fn_name, args[0], args[1])? { @@ -757,9 +700,9 @@ impl Engine { &self, scope: &mut Scope, state: &mut State, - lib: &FunctionsLib, + lib: &Module, fn_name: &str, - fn_def: &FnDef, + fn_def: &ScriptFnDef, args: &mut FnCallArgs, level: usize, ) -> Result> { @@ -809,13 +752,18 @@ impl Engine { } // Has a system function an override? - fn has_override(&self, lib: &FunctionsLib, hashes: (u64, u64)) -> bool { - // First check registered functions - self.global_module.contains_fn(hashes.0) - // Then check packages - || self.packages.contains_fn(hashes.0) - // Then check script-defined functions - || lib.contains_key(&hashes.1) + fn has_override(&self, lib: &Module, hashes: (u64, u64)) -> bool { + // NOTE: We skip script functions for global_module and packages, and native functions for lib + + // First check script-defined functions + lib.contains_fn(hashes.1) + //|| lib.contains_fn(hashes.0) + // Then check registered functions + //|| self.global_module.contains_fn(hashes.1) + || self.global_module.contains_fn(hashes.0) + // Then check packages + //|| self.packages.contains_fn(hashes.1) + || self.packages.contains_fn(hashes.0) } /// Perform an actual function call, taking care of special functions @@ -829,7 +777,7 @@ impl Engine { fn exec_fn_call( &self, state: &mut State, - lib: &FunctionsLib, + lib: &Module, fn_name: &str, native_only: bool, hash_fn_def: u64, @@ -878,7 +826,7 @@ impl Engine { &self, scope: &mut Scope, state: &mut State, - lib: &FunctionsLib, + lib: &Module, script: &Dynamic, ) -> Result> { let script = script.as_str().map_err(|type_name| { @@ -894,7 +842,7 @@ impl Engine { )?; // If new functions are defined within the eval string, it is an error - if !ast.lib().is_empty() { + if ast.lib().num_fn() != 0 { return Err(Box::new(EvalAltResult::ErrorParsing( ParseErrorType::WrongFnDefinition.into_err(Position::none()), ))); @@ -917,7 +865,7 @@ impl Engine { fn eval_dot_index_chain_helper( &self, state: &mut State, - lib: &FunctionsLib, + lib: &Module, target: &mut Target, rhs: &Expr, idx_values: &mut StaticVec, @@ -1097,7 +1045,7 @@ impl Engine { &self, scope: &mut Scope, state: &mut State, - lib: &FunctionsLib, + lib: &Module, expr: &Expr, level: usize, new_val: Option, @@ -1166,7 +1114,7 @@ impl Engine { &self, scope: &mut Scope, state: &mut State, - lib: &FunctionsLib, + lib: &Module, expr: &Expr, idx_values: &mut StaticVec, size: usize, @@ -1211,7 +1159,7 @@ impl Engine { fn get_indexed_mut<'a>( &self, state: &mut State, - lib: &FunctionsLib, + lib: &Module, val: &'a mut Dynamic, is_ref: bool, mut idx: Dynamic, @@ -1305,7 +1253,7 @@ impl Engine { &self, scope: &mut Scope, state: &mut State, - lib: &FunctionsLib, + lib: &Module, lhs: &Expr, rhs: &Expr, level: usize, @@ -1368,7 +1316,7 @@ impl Engine { &self, scope: &mut Scope, state: &mut State, - lib: &FunctionsLib, + lib: &Module, expr: &Expr, level: usize, ) -> Result> { @@ -1650,14 +1598,14 @@ impl Engine { }; match func { - Ok(x) if x.is_script() => { + Ok(f) if f.is_script() => { let args = args.as_mut(); - let fn_def = x.get_fn_def(); + let fn_def = f.get_fn_def(); let mut scope = Scope::new(); self.call_script_fn(&mut scope, state, lib, name, fn_def, args, level) .map_err(|err| EvalAltResult::new_position(err, *pos)) } - Ok(x) => x.get_native_fn()(args.as_mut()).map_err(|err| err.new_position(*pos)), + Ok(f) => f.get_native_fn()(args.as_mut()).map_err(|err| err.new_position(*pos)), Err(err) if def_val.is_some() && matches!(*err, EvalAltResult::ErrorFunctionNotFound(_, _)) => @@ -1719,7 +1667,7 @@ impl Engine { &self, scope: &mut Scope, state: &mut State, - lib: &FunctionsLib, + lib: &Module, stmt: &Stmt, level: usize, ) -> Result> { diff --git a/src/fn_native.rs b/src/fn_native.rs index 334068b5..bf6219e1 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -1,8 +1,8 @@ use crate::any::Dynamic; -use crate::parser::FnDef; +use crate::parser::ScriptFnDef; use crate::result::EvalAltResult; -use crate::stdlib::{boxed::Box, rc::Rc, sync::Arc}; +use crate::stdlib::{boxed::Box, fmt, rc::Rc, sync::Arc}; #[cfg(feature = "sync")] pub trait SendSync: Send + Sync {} @@ -73,7 +73,18 @@ pub enum CallableFunction { /// An iterator function. Iterator(IteratorFn), /// A script-defined function. - Script(Shared), + Script(Shared), +} + +impl fmt::Debug for CallableFunction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Pure(_) => write!(f, "NativePureFunction"), + Self::Method(_) => write!(f, "NativeMethod"), + Self::Iterator(_) => write!(f, "NativeIterator"), + Self::Script(fn_def) => write!(f, "{:?}", fn_def), + } + } } impl CallableFunction { @@ -116,12 +127,23 @@ impl CallableFunction { Self::Iterator(_) | Self::Script(_) => panic!(), } } + /// Get a shared reference to a script-defined function definition. + /// + /// # Panics + /// + /// Panics if the `CallableFunction` is not `Script`. + pub fn get_shared_fn_def(&self) -> Shared { + match self { + Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => panic!(), + Self::Script(f) => f.clone(), + } + } /// Get a reference to a script-defined function definition. /// /// # Panics /// /// Panics if the `CallableFunction` is not `Script`. - pub fn get_fn_def(&self) -> &FnDef { + pub fn get_fn_def(&self) -> &ScriptFnDef { match self { Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => panic!(), Self::Script(f) => f, @@ -147,3 +169,21 @@ impl CallableFunction { Self::Method(func.into()) } } + +impl From for CallableFunction { + fn from(func: IteratorFn) -> Self { + Self::Iterator(func) + } +} + +impl From for CallableFunction { + fn from(func: ScriptFnDef) -> Self { + Self::Script(func.into()) + } +} + +impl From> for CallableFunction { + fn from(func: Shared) -> Self { + Self::Script(func) + } +} diff --git a/src/module.rs b/src/module.rs index cd567a57..d509f3c1 100644 --- a/src/module.rs +++ b/src/module.rs @@ -2,12 +2,12 @@ use crate::any::{Dynamic, Variant}; use crate::calc_fn_hash; -use crate::engine::{make_getter, make_setter, Engine, FunctionsLib, FUNC_INDEXER}; +use crate::engine::{make_getter, make_setter, Engine, FUNC_INDEXER}; use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn, SendSync}; use crate::parser::{ FnAccess, FnAccess::{Private, Public}, - AST, + ScriptFnDef, AST, }; use crate::result::EvalAltResult; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; @@ -53,9 +53,6 @@ pub struct Module { StraightHasherBuilder, >, - /// Script-defined functions. - lib: FunctionsLib, - /// Iterator functions, keyed by the type producing the iterator. type_iterators: HashMap, @@ -68,10 +65,9 @@ impl fmt::Debug for Module { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "", + "", self.variables, self.functions.len(), - self.lib.len() ) } } @@ -186,6 +182,23 @@ impl Module { .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.to_string(), pos))) } + /// Set a script-defined function into the module. + /// + /// If there is an existing function of the same name and number of arguments, it is replaced. + pub(crate) fn set_script_fn(&mut self, fn_def: ScriptFnDef) { + // None + function name + number of arguments. + let hash_fn_def = calc_fn_hash(empty(), &fn_def.name, fn_def.params.len(), empty()); + self.functions.insert( + hash_fn_def, + ( + fn_def.name.to_string(), + fn_def.access, + Default::default(), + fn_def.into(), + ), + ); + } + /// Does a sub-module exist in the module? /// /// # Examples @@ -750,6 +763,41 @@ impl Module { }) } + /// Merge another module into this module. + pub fn merge(&mut self, other: &Self) { + self.variables + .extend(other.variables.iter().map(|(k, v)| (k.clone(), v.clone()))); + self.functions + .extend(other.functions.iter().map(|(&k, v)| (k, v.clone()))); + self.type_iterators + .extend(other.type_iterators.iter().map(|(&k, v)| (k, v.clone()))); + } + + /// Get the number of variables in the module. + pub fn num_var(&self) -> usize { + self.variables.len() + } + /// Get the number of functions in the module. + pub fn num_fn(&self) -> usize { + self.variables.len() + } + /// Get the number of type iterators in the module. + pub fn num_iter(&self) -> usize { + self.variables.len() + } + + /// Get an iterator to the variables in the module. + pub fn iter_var(&self) -> impl Iterator { + self.variables.iter() + } + + /// Get an iterator to the functions in the module. + pub(crate) fn iter_fn( + &self, + ) -> impl Iterator, CallableFunction)> { + self.functions.values() + } + /// Create a new `Module` by evaluating an `AST`. /// /// # Examples @@ -795,12 +843,12 @@ impl Module { }, ); - module.lib = module.lib.merge(ast.lib()); + module.merge(ast.lib()); Ok(module) } - /// Scan through all the sub-modules in the `Module` build an index of all + /// Scan through all the sub-modules in the module build an index of all /// variables and external Rust functions via hashing. pub(crate) fn index_all_sub_modules(&mut self) { // Collect a particular module. @@ -830,34 +878,31 @@ impl Module { Private => continue, Public => (), } - // Rust functions are indexed in two steps: - // 1) Calculate a hash in a similar manner to script-defined functions, - // i.e. qualifiers + function name + number of arguments. - let hash_fn_def = - calc_fn_hash(qualifiers.iter().map(|&v| v), name, params.len(), empty()); - // 2) Calculate a second hash with no qualifiers, empty function name, - // zero number of arguments, and the actual list of argument `TypeId`'.s - let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned()); - // 3) The final hash is the XOR of the two hashes. - let hash_fn_native = hash_fn_def ^ hash_fn_args; - functions.push((hash_fn_native, func.clone())); - } - // Index all script-defined functions - for fn_def in module.lib.values() { - match fn_def.access { - // Private functions are not exported - Private => continue, - Public => (), + if func.is_script() { + let fn_def = func.get_shared_fn_def(); + // Qualifiers + function name + number of arguments. + let hash_fn_def = calc_fn_hash( + qualifiers.iter().map(|&v| v), + &fn_def.name, + fn_def.params.len(), + empty(), + ); + functions.push((hash_fn_def, fn_def.into())); + } else { + // Rust functions are indexed in two steps: + // 1) Calculate a hash in a similar manner to script-defined functions, + // i.e. qualifiers + function name + number of arguments. + let hash_fn_def = + calc_fn_hash(qualifiers.iter().map(|&v| v), name, params.len(), empty()); + // 2) Calculate a second hash with no qualifiers, empty function name, + // zero number of arguments, and the actual list of argument `TypeId`'.s + let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned()); + // 3) The final hash is the XOR of the two hashes. + let hash_fn_native = hash_fn_def ^ hash_fn_args; + + functions.push((hash_fn_native, func.clone())); } - // Qualifiers + function name + number of arguments. - let hash_fn_def = calc_fn_hash( - qualifiers.iter().map(|&v| v), - &fn_def.name, - fn_def.params.len(), - empty(), - ); - functions.push((hash_fn_def, CallableFunction::Script(fn_def.clone()).into())); } } diff --git a/src/optimize.rs b/src/optimize.rs index ea27620d..7895fc68 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -1,10 +1,8 @@ use crate::any::Dynamic; use crate::calc_fn_hash; -use crate::engine::{ - Engine, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, - KEYWORD_TYPE_OF, -}; -use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST}; +use crate::engine::{Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF}; +use crate::module::Module; +use crate::parser::{map_dynamic_to_expr, Expr, ReturnType, ScriptFnDef, Stmt, AST}; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; use crate::utils::StaticVec; @@ -54,14 +52,14 @@ struct State<'a> { /// An `Engine` instance for eager function evaluation. engine: &'a Engine, /// Library of script-defined functions. - lib: &'a FunctionsLib, + lib: &'a Module, /// Optimization level. optimization_level: OptimizationLevel, } impl<'a> State<'a> { /// Create a new State. - pub fn new(engine: &'a Engine, lib: &'a FunctionsLib, level: OptimizationLevel) -> Self { + pub fn new(engine: &'a Engine, lib: &'a Module, level: OptimizationLevel) -> Self { Self { changed: false, constants: vec![], @@ -547,14 +545,15 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { && state.optimization_level == OptimizationLevel::Full // full optimizations && x.3.iter().all(|expr| expr.is_constant()) // all arguments are constants => { - let ((name, native_only, pos), _, _, args, def_value) = x.as_mut(); + let ((name, _, pos), _, _, args, def_value) = x.as_mut(); - // First search in script-defined functions (can override built-in) + // First search in functions lib (can override built-in) // Cater for both normal function call style and method call style (one additional arguments) - if !*native_only && state.lib.values().find(|f| - &f.name == name - && (args.len()..=args.len() + 1).contains(&f.params.len()) - ).is_some() { + if state.lib.iter_fn().find(|(_, _, _, f)| { + if !f.is_script() { return false; } + let fn_def = f.get_fn_def(); + &fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len()) + }).is_some() { // A script-defined function overrides the built-in function - do not make the call x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect(); return Expr::FnCall(x); @@ -616,7 +615,7 @@ fn optimize( statements: Vec, engine: &Engine, scope: &Scope, - lib: &FunctionsLib, + lib: &Module, level: OptimizationLevel, ) -> Vec { // If optimization level is None then skip optimizing @@ -703,7 +702,7 @@ pub fn optimize_into_ast( engine: &Engine, scope: &Scope, statements: Vec, - functions: Vec, + functions: Vec, level: OptimizationLevel, ) -> AST { #[cfg(feature = "no_optimize")] @@ -711,37 +710,65 @@ pub fn optimize_into_ast( #[cfg(not(feature = "no_function"))] let lib = { + let mut module = Module::new(); + if !level.is_none() { - let lib = FunctionsLib::from_iter(functions.iter().cloned()); + // We only need the script library's signatures for optimization purposes + let mut lib2 = Module::new(); - FunctionsLib::from_iter(functions.into_iter().map(|mut fn_def| { - let pos = fn_def.body.position(); - - // Optimize the function body - let mut body = optimize(vec![fn_def.body], engine, &Scope::new(), &lib, level); - - // {} -> Noop - fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) { - // { return val; } -> val - Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => { - Stmt::Expr(Box::new(x.1.unwrap())) + functions + .iter() + .map(|fn_def| { + ScriptFnDef { + name: fn_def.name.clone(), + access: fn_def.access, + body: Default::default(), + params: fn_def.params.clone(), + pos: fn_def.pos, } - // { return; } -> () - Stmt::ReturnWithVal(x) if x.1.is_none() && (x.0).0 == ReturnType::Return => { - Stmt::Expr(Box::new(Expr::Unit((x.0).1))) - } - // All others - stmt => stmt, - }; - fn_def - })) + .into() + }) + .for_each(|fn_def| lib2.set_script_fn(fn_def)); + + functions + .into_iter() + .map(|mut fn_def| { + let pos = fn_def.body.position(); + + // Optimize the function body + let mut body = optimize(vec![fn_def.body], engine, &Scope::new(), &lib2, level); + + // {} -> Noop + fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) { + // { return val; } -> val + Stmt::ReturnWithVal(x) + if x.1.is_some() && (x.0).0 == ReturnType::Return => + { + Stmt::Expr(Box::new(x.1.unwrap())) + } + // { return; } -> () + Stmt::ReturnWithVal(x) + if x.1.is_none() && (x.0).0 == ReturnType::Return => + { + Stmt::Expr(Box::new(Expr::Unit((x.0).1))) + } + // All others + stmt => stmt, + }; + fn_def.into() + }) + .for_each(|fn_def| module.set_script_fn(fn_def)); } else { - FunctionsLib::from_iter(functions.into_iter()) + functions + .into_iter() + .for_each(|fn_def| module.set_script_fn(fn_def)); } + + module }; #[cfg(feature = "no_function")] - let lib: FunctionsLib = Default::default(); + let lib = Default::default(); AST::new( match level { diff --git a/src/parser.rs b/src/parser.rs index b2c4c1cf..ab8de95c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,9 +2,9 @@ use crate::any::{Dynamic, Union}; use crate::calc_fn_hash; -use crate::engine::{make_getter, make_setter, Engine, FunctionsLib}; +use crate::engine::{make_getter, make_setter, Engine}; use crate::error::{LexError, ParseError, ParseErrorType}; -use crate::module::ModuleRef; +use crate::module::{Module, ModuleRef}; use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::token::{Position, Token, TokenIterator}; @@ -55,12 +55,12 @@ pub struct AST( /// Global statements. Vec, /// Script-defined functions. - FunctionsLib, + Module, ); impl AST { /// Create a new `AST`. - pub fn new(statements: Vec, lib: FunctionsLib) -> Self { + pub fn new(statements: Vec, lib: Module) -> Self { Self(statements, lib) } @@ -75,7 +75,7 @@ impl AST { } /// Get the script-defined functions. - pub(crate) fn lib(&self) -> &FunctionsLib { + pub(crate) fn lib(&self) -> &Module { &self.1 } @@ -135,7 +135,10 @@ impl AST { (true, true) => vec![], }; - Self::new(ast, functions.merge(&other.1)) + let mut functions = functions.clone(); + functions.merge(&other.1); + + Self::new(ast, functions) } /// Clear all function definitions in the `AST`. @@ -170,7 +173,7 @@ pub enum FnAccess { /// A scripted function definition. #[derive(Debug, Clone)] -pub struct FnDef { +pub struct ScriptFnDef { /// Function name. pub name: String, /// Function access mode. @@ -2393,7 +2396,7 @@ fn parse_fn<'a>( level: usize, if_expr: bool, stmt_expr: bool, -) -> Result { +) -> Result { let pos = eat_token(input, Token::Fn); if level > state.max_expr_depth { @@ -2469,7 +2472,7 @@ fn parse_fn<'a>( let params = params.into_iter().map(|(p, _)| p).collect(); - Ok(FnDef { + Ok(ScriptFnDef { name, access, params, @@ -2483,9 +2486,9 @@ fn parse_global_level<'a>( input: &mut Peekable>, max_expr_depth: usize, max_function_expr_depth: usize, -) -> Result<(Vec, Vec), ParseError> { +) -> Result<(Vec, Vec), ParseError> { let mut statements = Vec::::new(); - let mut functions = HashMap::::with_hasher(StraightHasherBuilder); + let mut functions = HashMap::::with_hasher(StraightHasherBuilder); let mut state = ParseState::new(max_expr_depth); while !input.peek().unwrap().0.is_eof() {