From 8b5550eeb6b5c98e4e86867810d9c69395c63b22 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 17 May 2020 22:19:49 +0800 Subject: [PATCH] Complete StaticVec implementation. --- README.md | 5 +- src/api.rs | 8 +- src/engine.rs | 41 +++-- src/module.rs | 13 +- src/optimize.rs | 57 +++---- src/parser.rs | 10 +- src/token.rs | 3 +- src/unsafe.rs | 5 - src/utils.rs | 360 ++++++++++++++++++++++++++++++++------------ tests/modules.rs | 16 +- tests/operations.rs | 24 ++- 11 files changed, 373 insertions(+), 169 deletions(-) diff --git a/README.md b/README.md index be949d40..46bae69f 100644 --- a/README.md +++ b/README.md @@ -2288,8 +2288,9 @@ engine.set_max_operations(0); // allow unlimited operations ``` The concept of one single _operation_ in Rhai is volatile - it roughly equals one expression node, -one statement, one iteration of a loop, or one function call etc. with sub-expressions and statement -blocks executed inside these contexts accumulated on top. +loading a variable/constant, one operator call, one complete statement, one iteration of a loop, +or one function call etc. with sub-expressions and statement blocks executed inside these contexts accumulated on top. +A good rule-of-thumb is that one simple non-trivial expression consumes on average 5-10 operations. One _operation_ can take an unspecified amount of time and CPU cycles, depending on the particular operation involved. For example, loading a constant consumes very few CPU cycles, while calling an external Rust function, diff --git a/src/api.rs b/src/api.rs index 5e144bcf..26a825ab 100644 --- a/src/api.rs +++ b/src/api.rs @@ -652,7 +652,10 @@ impl Engine { let scripts = [script]; let stream = lex(&scripts); - parse_global_expr(&mut stream.peekable(), self, scope, self.optimization_level) + { + let mut peekable = stream.peekable(); + parse_global_expr(&mut peekable, self, scope, self.optimization_level) + } } /// Evaluate a script file. @@ -748,11 +751,10 @@ impl Engine { scope: &mut Scope, script: &str, ) -> Result> { - // Since the AST will be thrown away afterwards, don't bother to optimize it let ast = self.compile_with_scope_and_optimization_level( scope, &[script], - OptimizationLevel::None, + self.optimization_level, )?; self.eval_ast_with_scope(scope, &ast) } diff --git a/src/engine.rs b/src/engine.rs index 534a506a..4c91f39c 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -119,11 +119,11 @@ impl Target<'_> { .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; let mut chars: StaticVec = s.chars().collect(); - let ch = *chars.get_ref(*index); + let ch = chars[*index]; // See if changed - if so, update the String if ch != new_ch { - *chars.get_mut(*index) = new_ch; + chars[*index] = new_ch; s.clear(); chars.iter().for_each(|&ch| s.push(ch)); } @@ -190,7 +190,7 @@ impl<'a> State<'a> { } /// Get a script-defined function definition from the `State`. pub fn get_function(&self, hash: u64) -> Option<&FnDef> { - self.fn_lib.get(&hash).map(|f| f.as_ref()) + self.fn_lib.get(&hash).map(|fn_def| fn_def.as_ref()) } } @@ -463,7 +463,7 @@ fn search_scope<'a>( .downcast_mut::() .unwrap() } else { - let (id, root_pos) = modules.get_ref(0); + let (id, root_pos) = modules.get(0); scope.find_module(id).ok_or_else(|| { Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)) @@ -736,8 +736,6 @@ impl Engine { pos: Position, level: usize, ) -> Result<(Dynamic, State<'s>), Box> { - self.inc_operations(&mut state, pos)?; - let orig_scope_level = state.scope_level; state.scope_level += 1; @@ -1120,6 +1118,7 @@ impl Engine { let index = if state.always_search { None } else { *index }; let mod_and_hash = modules.as_ref().map(|m| (m, *hash_var)); let (target, typ) = search_scope(scope, &name, mod_and_hash, index, *pos)?; + self.inc_operations(state, *pos)?; // Constants cannot be modified match typ { @@ -1171,6 +1170,8 @@ impl Engine { size: usize, level: usize, ) -> Result<(), Box> { + self.inc_operations(state, expr.position())?; + match expr { Expr::FnCall(x) if x.1.is_none() => { let arg_values = @@ -1328,13 +1329,13 @@ impl Engine { #[cfg(not(feature = "no_object"))] Dynamic(Union::Map(rhs_value)) => match lhs_value { // Only allows String or char - Dynamic(Union::Str(s)) => Ok(rhs_value.contains_key(s.as_ref()).into()), + Dynamic(Union::Str(s)) => Ok(rhs_value.contains_key(s.as_str()).into()), Dynamic(Union::Char(c)) => Ok(rhs_value.contains_key(&c.to_string()).into()), _ => Err(Box::new(EvalAltResult::ErrorInExpr(lhs.position()))), }, Dynamic(Union::Str(rhs_value)) => match lhs_value { // Only allows String or char - Dynamic(Union::Str(s)) => Ok(rhs_value.contains(s.as_ref()).into()), + Dynamic(Union::Str(s)) => Ok(rhs_value.contains(s.as_str()).into()), Dynamic(Union::Char(c)) => Ok(rhs_value.contains(c).into()), _ => Err(Box::new(EvalAltResult::ErrorInExpr(lhs.position()))), }, @@ -1382,6 +1383,8 @@ impl Engine { let index = if state.always_search { None } else { *index }; let mod_and_hash = modules.as_ref().map(|m| (m, *hash_var)); let (lhs_ptr, typ) = search_scope(scope, name, mod_and_hash, index, *pos)?; + self.inc_operations(state, *pos)?; + match typ { ScopeEntryType::Constant => Err(Box::new( EvalAltResult::ErrorAssignmentToConstant(name.clone(), *pos), @@ -1465,13 +1468,13 @@ impl Engine { let mut args: StaticVec<_> = arg_values.iter_mut().collect(); - if name == KEYWORD_EVAL && args.len() == 1 && args.get_ref(0).is::() { + if name == KEYWORD_EVAL && args.len() == 1 && args.get(0).is::() { let hash_fn = calc_fn_hash(empty(), name, once(TypeId::of::())); if !self.has_override(state, (hash_fn, *hash_fn_def)) { // eval - only in function call style let prev_len = scope.len(); - let pos = args_expr.get_ref(0).position(); + let pos = args_expr.get(0).position(); // Evaluate the text string as a script let result = self.eval_script_expr(scope, state, args.pop(), pos); @@ -1505,7 +1508,7 @@ impl Engine { let mut args: StaticVec<_> = arg_values.iter_mut().collect(); - let (id, root_pos) = modules.get_ref(0); // First module + let (id, root_pos) = modules.get(0); // First module let module = if let Some(index) = modules.index() { scope @@ -1663,9 +1666,7 @@ impl Engine { match self.eval_expr(scope, state, expr, level)?.as_bool() { Ok(true) => match self.eval_stmt(scope, state, body, level) { - Ok(_) => { - self.inc_operations(state, body.position())?; - } + Ok(_) => (), Err(err) => match *err { EvalAltResult::ErrorLoopBreak(false, _) => (), EvalAltResult::ErrorLoopBreak(true, _) => return Ok(Default::default()), @@ -1682,9 +1683,7 @@ impl Engine { // Loop statement Stmt::Loop(body) => loop { match self.eval_stmt(scope, state, body, level) { - Ok(_) => { - self.inc_operations(state, body.position())?; - } + Ok(_) => (), Err(err) => match *err { EvalAltResult::ErrorLoopBreak(false, _) => (), EvalAltResult::ErrorLoopBreak(true, _) => return Ok(Default::default()), @@ -1773,7 +1772,6 @@ impl Engine { let val = self.eval_expr(scope, state, expr.as_ref().unwrap(), level)?; let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state); scope.push_dynamic_value(var_name, ScopeEntryType::Normal, val, false); - self.inc_operations(state, *pos)?; Ok(Default::default()) } @@ -1781,7 +1779,6 @@ impl Engine { let ((var_name, pos), _) = x.as_ref(); let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state); scope.push(var_name, ()); - self.inc_operations(state, *pos)?; Ok(Default::default()) } @@ -1791,7 +1788,6 @@ impl Engine { let val = self.eval_expr(scope, state, &expr, level)?; let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state); scope.push_dynamic_value(var_name, ScopeEntryType::Constant, val, true); - self.inc_operations(state, *pos)?; Ok(Default::default()) } @@ -1818,7 +1814,7 @@ impl Engine { .eval_expr(scope, state, &expr, level)? .try_cast::() { - if let Some(resolver) = self.module_resolver.as_ref() { + if let Some(resolver) = &self.module_resolver { // Use an empty scope to create a module let module = resolver.resolve(self, Scope::new(), &path, expr.position())?; @@ -1827,7 +1823,6 @@ impl Engine { scope.push_module(mod_name, module); state.modules += 1; - self.inc_operations(state, *pos)?; Ok(Default::default()) } else { @@ -1884,7 +1879,7 @@ impl Engine { } // Report progress - only in steps - if let Some(progress) = self.progress.as_ref() { + if let Some(progress) = &self.progress { if !progress(state.operations) { // Terminate script if progress returns false return Err(Box::new(EvalAltResult::ErrorTerminated(pos))); diff --git a/src/module.rs b/src/module.rs index 056d1a75..377a4357 100644 --- a/src/module.rs +++ b/src/module.rs @@ -51,7 +51,7 @@ pub struct Module { all_variables: HashMap, /// External Rust functions. - functions: HashMap, SharedNativeFunction)>, + functions: HashMap, SharedNativeFunction)>, /// Flattened collection of all external Rust functions, including those in sub-modules. all_functions: HashMap, @@ -292,8 +292,9 @@ impl Module { #[cfg(feature = "sync")] let func = Arc::new(f); - self.functions - .insert(hash_fn, (name, access, params.to_vec(), func)); + let params = params.into_iter().cloned().collect(); + + self.functions.insert(hash_fn, (name, access, params, func)); hash_fn } @@ -616,13 +617,13 @@ impl Module { pub(crate) fn index_all_sub_modules(&mut self) { // Collect a particular module. fn index_module<'a>( - module: &'a mut Module, + module: &'a Module, qualifiers: &mut Vec<&'a str>, variables: &mut Vec<(u64, Dynamic)>, functions: &mut Vec<(u64, SharedNativeFunction)>, fn_lib: &mut Vec<(u64, SharedFnDef)>, ) { - for (name, m) in module.modules.iter_mut() { + for (name, m) in &module.modules { // Index all the sub-modules first. qualifiers.push(name); index_module(m, qualifiers, variables, functions, fn_lib); @@ -630,7 +631,7 @@ impl Module { } // Index all variables - for (var_name, value) in module.variables.iter() { + for (var_name, value) in &module.variables { // Qualifiers + variable name let hash_var = calc_fn_hash(qualifiers.iter().map(|&v| v), var_name, empty()); variables.push((hash_var, value.clone())); diff --git a/src/optimize.rs b/src/optimize.rs index f118fc3a..671415e3 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -236,24 +236,24 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) - // import expr as id; Stmt::Import(x) => Stmt::Import(Box::new((optimize_expr(x.0, state), x.1))), // { block } - Stmt::Block(mut x) => { + Stmt::Block(x) => { let orig_len = x.0.len(); // Original number of statements in the block, for change detection let orig_constants_len = state.constants.len(); // Original number of constants in the state, for restore later let pos = x.1; // Optimize each statement in the block let mut result: Vec<_> = - x.0.iter_mut() + x.0.into_iter() .map(|stmt| match stmt { // Add constant into the state Stmt::Const(v) => { - let ((name, pos), expr) = v.as_mut(); - state.push_constant(name, mem::take(expr)); + let ((name, pos), expr) = *v; + state.push_constant(&name, expr); state.set_dirty(); - Stmt::Noop(*pos) // No need to keep constants + Stmt::Noop(pos) // No need to keep constants } // Optimize the statement - _ => optimize_stmt(mem::take(stmt), state, preserve_result), + _ => optimize_stmt(stmt, state, preserve_result), }) .collect(); @@ -399,14 +399,14 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { #[cfg(not(feature = "no_object"))] Expr::Dot(x) => match (x.0, x.1) { // map.string - (Expr::Map(mut m), Expr::Property(p)) if m.0.iter().all(|(_, x)| x.is_pure()) => { + (Expr::Map(m), Expr::Property(p)) if m.0.iter().all(|(_, x)| x.is_pure()) => { let ((prop, _, _), _) = p.as_ref(); // Map literal where everything is pure - promote the indexed item. // All other items can be thrown away. state.set_dirty(); let pos = m.1; - m.0.iter_mut().find(|((name, _), _)| name == prop) - .map(|(_, expr)| mem::take(expr).set_position(pos)) + m.0.into_iter().find(|((name, _), _)| name == prop) + .map(|(_, expr)| expr.set_position(pos)) .unwrap_or_else(|| Expr::Unit(pos)) } // lhs.rhs @@ -423,16 +423,16 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { // Array literal where everything is pure - promote the indexed item. // All other items can be thrown away. state.set_dirty(); - a.0.get(i.0 as usize).set_position(a.1) + a.0.take(i.0 as usize).set_position(a.1) } // map[string] - (Expr::Map(mut m), Expr::StringConstant(s)) if m.0.iter().all(|(_, x)| x.is_pure()) => { + (Expr::Map(m), Expr::StringConstant(s)) if m.0.iter().all(|(_, x)| x.is_pure()) => { // Map literal where everything is pure - promote the indexed item. // All other items can be thrown away. state.set_dirty(); let pos = m.1; - m.0.iter_mut().find(|((name, _), _)| name == &s.0) - .map(|(_, expr)| mem::take(expr).set_position(pos)) + m.0.into_iter().find(|((name, _), _)| name == &s.0) + .map(|(_, expr)| expr.set_position(pos)) .unwrap_or_else(|| Expr::Unit(pos)) } // string[int] @@ -446,13 +446,13 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { }, // [ items .. ] #[cfg(not(feature = "no_index"))] - Expr::Array(mut a) => Expr::Array(Box::new((a.0 - .iter_mut().map(|expr| optimize_expr(mem::take(expr), state)) + Expr::Array(a) => Expr::Array(Box::new((a.0 + .into_iter().map(|expr| optimize_expr(expr, state)) .collect(), a.1))), // [ items .. ] #[cfg(not(feature = "no_object"))] - Expr::Map(mut m) => Expr::Map(Box::new((m.0 - .iter_mut().map(|((key, pos), expr)| ((mem::take(key), *pos), optimize_expr(mem::take(expr), state))) + Expr::Map(m) => Expr::Map(Box::new((m.0 + .into_iter().map(|((key, pos), expr)| ((key, pos), optimize_expr(expr, state))) .collect(), m.1))), // lhs in rhs Expr::In(x) => match (x.0, x.1) { @@ -532,7 +532,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { // Do not call some special keywords Expr::FnCall(mut x) if DONT_EVAL_KEYWORDS.contains(&(x.0).0.as_ref())=> { - x.3 = x.3.iter_mut().map(|a| optimize_expr(mem::take(a), state)).collect(); + x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect(); Expr::FnCall(x) } @@ -547,12 +547,12 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { // First search in script-defined functions (can override built-in) if state.fn_lib.iter().find(|(id, len)| *id == name && *len == args.len()).is_some() { // A script-defined function overrides the built-in function - do not make the call - x.3 = x.3.iter_mut().map(|a| optimize_expr(mem::take(a), state)).collect(); + x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect(); return Expr::FnCall(x); } - let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect(); - let mut call_args: Vec<_> = arg_values.iter_mut().collect(); + let mut arg_values: StaticVec<_> = args.iter().map(Expr::get_constant_value).collect(); + let mut call_args: StaticVec<_> = arg_values.iter_mut().collect(); // Save the typename of the first argument if it is `type_of()` // This is to avoid `call_args` being passed into the closure @@ -562,7 +562,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { "" }; - call_fn(&state.engine.packages, &state.engine.global_module, name, &mut call_args, *pos).ok() + call_fn(&state.engine.packages, &state.engine.global_module, name, call_args.as_mut(), *pos).ok() .and_then(|result| result.or_else(|| { if !arg_for_type_of.is_empty() { @@ -579,14 +579,14 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { }) ).unwrap_or_else(|| { // Optimize function call arguments - x.3 = x.3.iter_mut().map(|a| optimize_expr(mem::take(a), state)).collect(); + x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect(); Expr::FnCall(x) }) } // id(args ..) -> optimize function call arguments Expr::FnCall(mut x) => { - x.3 = x.3.iter_mut().map(|a| optimize_expr(mem::take(a), state)).collect(); + x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect(); Expr::FnCall(x) } @@ -702,11 +702,14 @@ pub fn optimize_into_ast( const level: OptimizationLevel = OptimizationLevel::None; #[cfg(not(feature = "no_function"))] - let fn_lib: Vec<_> = functions + let fn_lib_values: StaticVec<_> = functions .iter() .map(|fn_def| (fn_def.name.as_str(), fn_def.params.len())) .collect(); + #[cfg(not(feature = "no_function"))] + let fn_lib = fn_lib_values.as_ref(); + #[cfg(feature = "no_function")] const fn_lib: &[(&str, usize)] = &[]; @@ -716,7 +719,7 @@ pub fn optimize_into_ast( let pos = fn_def.body.position(); // Optimize the function body - let mut body = optimize(vec![fn_def.body], engine, &Scope::new(), &fn_lib, level); + let mut body = optimize(vec![fn_def.body], engine, &Scope::new(), fn_lib, level); // {} -> Noop fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) { @@ -742,7 +745,7 @@ pub fn optimize_into_ast( match level { OptimizationLevel::None => statements, OptimizationLevel::Simple | OptimizationLevel::Full => { - optimize(statements, engine, &scope, &fn_lib, level) + optimize(statements, engine, &scope, fn_lib, level) } }, lib, diff --git a/src/parser.rs b/src/parser.rs index 9e608b79..29c955e2 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -330,7 +330,7 @@ impl Stmt { Stmt::Loop(x) => x.position(), Stmt::For(x) => x.2.position(), Stmt::Import(x) => (x.1).1, - Stmt::Export(x) => (x.get_ref(0).0).1, + Stmt::Export(x) => (x.get(0).0).1, } } @@ -745,7 +745,7 @@ fn parse_call_expr<'a>( #[cfg(not(feature = "no_module"))] let hash_fn_def = { if let Some(modules) = modules.as_mut() { - modules.set_index(stack.find_module(&modules.get_ref(0).0)); + modules.set_index(stack.find_module(&modules.get(0).0)); // Rust functions are indexed in two steps: // 1) Calculate a hash in a similar manner to script-defined functions, @@ -786,7 +786,7 @@ fn parse_call_expr<'a>( #[cfg(not(feature = "no_module"))] let hash_fn_def = { if let Some(modules) = modules.as_mut() { - modules.set_index(stack.find_module(&modules.get_ref(0).0)); + modules.set_index(stack.find_module(&modules.get(0).0)); // Rust functions are indexed in two steps: // 1) Calculate a hash in a similar manner to script-defined functions, @@ -1251,7 +1251,7 @@ fn parse_primary<'a>( // Qualifiers + variable name *hash = calc_fn_hash(modules.iter().map(|(v, _)| v.as_str()), name, empty()); - modules.set_index(stack.find_module(&modules.get_ref(0).0)); + modules.set_index(stack.find_module(&modules.get(0).0)); } _ => (), } @@ -1471,7 +1471,7 @@ fn make_dot_expr( #[cfg(feature = "no_module")] unreachable!(); #[cfg(not(feature = "no_module"))] - return Err(PERR::PropertyExpected.into_err(x.1.unwrap().get_ref(0).1)); + return Err(PERR::PropertyExpected.into_err(x.1.unwrap().get(0).1)); } // lhs.dot_lhs.dot_rhs (lhs, Expr::Dot(x)) => { diff --git a/src/token.rs b/src/token.rs index 25d26ff7..97df2d54 100644 --- a/src/token.rs +++ b/src/token.rs @@ -2,6 +2,7 @@ use crate::error::LexError; use crate::parser::INT; +use crate::utils::StaticVec; #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; @@ -425,7 +426,7 @@ pub struct TokenIterator<'a> { /// Current position. pos: Position, /// The input character streams. - streams: Vec>>, + streams: StaticVec>>, } impl<'a> TokenIterator<'a> { diff --git a/src/unsafe.rs b/src/unsafe.rs index efbf80c7..46d91198 100644 --- a/src/unsafe.rs +++ b/src/unsafe.rs @@ -61,8 +61,3 @@ pub fn unsafe_cast_var_name_to_lifetime<'s>(name: &str, state: &State) -> Cow<'s name.to_string().into() } } - -/// Provide a type instance that is uninitialized. -pub fn unsafe_uninit() -> T { - unsafe { mem::MaybeUninit::uninit().assume_init() } -} diff --git a/src/utils.rs b/src/utils.rs index e7e62eeb..a8f73390 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,9 +2,7 @@ //! //! # Safety //! -//! The `StaticVec` type has some `unsafe` blocks. - -use crate::r#unsafe::unsafe_uninit; +//! The `StaticVec` type has some `unsafe` blocks to handle conversions between `MaybeUninit` and regular types. use crate::stdlib::{ any::TypeId, @@ -12,7 +10,8 @@ use crate::stdlib::{ hash::{Hash, Hasher}, iter::FromIterator, mem, - ops::Drop, + mem::MaybeUninit, + ops::{Drop, Index, IndexMut}, vec::Vec, }; @@ -52,13 +51,41 @@ pub fn calc_fn_spec<'a>( s.finish() } -const MAX_STATIC_VEC: usize = 4; - -/// A type to hold a number of values in static storage for speed, and any spill-overs in a `Vec`. +/// A type to hold a number of values in static storage for no-allocation, quick access. +/// If too many items are stored, it converts into using a `Vec`. /// /// This is essentially a knock-off of the [`staticvec`](https://crates.io/crates/staticvec) crate. /// This simplified implementation here is to avoid pulling in another crate. /// +/// # Implementation +/// +/// A `StaticVec` holds data in _either one_ of two storages: 1) a fixed-size array of `MAX_STATIC_VEC` +/// items, and 2) a dynamic `Vec`. At any time, either one of them (or both) must be empty, depending on the +/// total number of items. +/// +/// There is a `len` field containing the total number of items held by the `StaticVec`. +/// +/// The fixed-size array (`list`) is not initialized (i.e. initialized with `MaybeUninit::uninit()`). +/// +/// When `len <= MAX_STATIC_VEC`, all elements are stored in the fixed-size array. +/// Array slots `>= len` are `MaybeUninit::uninit()` while slots `< len` are considered actual data. +/// In this scenario, the `Vec` (`more`) is empty. +/// +/// As soon as we try to push a new item into the `StaticVec` that makes the total number exceed +/// `MAX_STATIC_VEC`, all the items in the fixed-sized array are taken out, replaced with +/// `MaybeUninit::uninit()` (via `mem::replace`) and pushed into the `Vec`. +/// Then the new item is added to the `Vec`. +/// +/// Therefore, if `len > MAX_STATIC_VEC`, then the fixed-size array (`list`) is considered +/// empty and uninitialized while all data resides in the `Vec` (`more`). +/// +/// When popping an item off of the `StaticVec`, the reverse is true. When `len = MAX_STATIC_VEC + 1`, +/// after popping the item, all the items residing in the `Vec` are moved back to the fixed-size array (`list`). +/// The `Vec` will then be empty. +/// +/// Therefore, if `len <= MAX_STATIC_VEC`, data is in the fixed-size array (`list`). +/// Otherwise, data is in the `Vec` (`more`). +/// /// # Safety /// /// This type uses some unsafe code (mainly for uninitialized/unused array slots) for efficiency. @@ -67,12 +94,16 @@ const MAX_STATIC_VEC: usize = 4; pub struct StaticVec { /// Total number of values held. len: usize, - /// Static storage. 4 slots should be enough for most cases - i.e. four items of fast, no-allocation access. - list: [mem::MaybeUninit; MAX_STATIC_VEC], + /// Fixed-size storage for fast, no-allocation access. + list: [MaybeUninit; MAX_STATIC_VEC], /// Dynamic storage. For spill-overs. more: Vec, } +/// Maximum slots of fixed-size storage for a `StaticVec`. +/// 4 slots should be enough for most cases. +const MAX_STATIC_VEC: usize = 4; + impl Drop for StaticVec { fn drop(&mut self) { self.clear(); @@ -83,7 +114,7 @@ impl Default for StaticVec { fn default() -> Self { Self { len: 0, - list: unsafe_uninit(), + list: unsafe { mem::MaybeUninit::uninit().assume_init() }, more: Vec::new(), } } @@ -91,9 +122,18 @@ impl Default for StaticVec { impl PartialEq for StaticVec { fn eq(&self, other: &Self) -> bool { - self.len == other.len - //&& self.list[0..self.len] == other.list[0..self.len] - && self.more == other.more + if self.len != other.len || self.more != other.more { + return false; + } + + if self.len > MAX_STATIC_VEC { + return true; + } + + unsafe { + mem::transmute::<_, &[T; MAX_STATIC_VEC]>(&self.list) + == mem::transmute::<_, &[T; MAX_STATIC_VEC]>(&other.list) + } } } @@ -102,10 +142,10 @@ impl Clone for StaticVec { let mut value: Self = Default::default(); value.len = self.len; - if self.len <= self.list.len() { + if self.is_fixed_storage() { for x in 0..self.len { let item: &T = unsafe { mem::transmute(self.list.get(x).unwrap()) }; - value.list[x] = mem::MaybeUninit::new(item.clone()); + value.list[x] = MaybeUninit::new(item.clone()); } } else { value.more = self.more.clone(); @@ -130,72 +170,118 @@ impl FromIterator for StaticVec { } impl StaticVec { - fn extract(value: mem::MaybeUninit) -> T { - unsafe { value.assume_init() } - } /// Create a new `StaticVec`. pub fn new() -> Self { Default::default() } /// Empty the `StaticVec`. pub fn clear(&mut self) { - if self.len <= self.list.len() { + if self.is_fixed_storage() { for x in 0..self.len { - Self::extract(mem::replace( - self.list.get_mut(x).unwrap(), - mem::MaybeUninit::uninit(), - )); + self.extract_from_list(x); } } else { self.more.clear(); } self.len = 0; } + /// Extract a `MaybeUninit` into a concrete initialized type. + fn extract(value: MaybeUninit) -> T { + unsafe { value.assume_init() } + } + /// Extract an item from the fixed-size array, replacing it with `MaybeUninit::uninit()`. + /// + /// # Panics + /// + /// Panics if fixed-size storage is not used, or if the `index` is out of bounds. + fn extract_from_list(&mut self, index: usize) -> T { + if !self.is_fixed_storage() { + panic!("not fixed storage in StaticVec"); + } + if index >= self.len { + panic!("index OOB in StaticVec"); + } + Self::extract(mem::replace( + self.list.get_mut(index).unwrap(), + MaybeUninit::uninit(), + )) + } + /// Set an item into the fixed-size array. + /// If `drop` is `true`, the original value is extracted then automatically dropped. + /// + /// # Panics + /// + /// Panics if fixed-size storage is not used, or if the `index` is out of bounds. + fn set_into_list(&mut self, index: usize, value: T, drop: bool) { + if !self.is_fixed_storage() { + panic!("not fixed storage in StaticVec"); + } + // Allow setting at most one slot to the right + if index > self.len { + panic!("index OOB in StaticVec"); + } + let temp = mem::replace(self.list.get_mut(index).unwrap(), MaybeUninit::new(value)); + if drop { + // Extract the original value - which will drop it automatically + Self::extract(temp); + } + } + /// Move item in the fixed-size array into the `Vec`. + /// + /// # Panics + /// + /// Panics if fixed-size storage is not used, or if the fixed-size storage is not full. + fn move_fixed_into_vec(&mut self, num: usize) { + if !self.is_fixed_storage() { + panic!("not fixed storage in StaticVec"); + } + if self.len != num { + panic!("fixed storage is not full in StaticVec"); + } + self.more.extend( + self.list + .iter_mut() + .take(num) + .map(|v| mem::replace(v, MaybeUninit::uninit())) + .map(Self::extract), + ); + } + /// Is data stored in fixed-size storage? + fn is_fixed_storage(&self) -> bool { + self.len <= MAX_STATIC_VEC + } /// Push a new value to the end of this `StaticVec`. pub fn push>(&mut self, value: X) { - if self.len == self.list.len() { - // Move the fixed list to the Vec - self.more.extend( - self.list - .iter_mut() - .map(|v| mem::replace(v, mem::MaybeUninit::uninit())) - .map(Self::extract), - ); + if self.len == MAX_STATIC_VEC { + self.move_fixed_into_vec(MAX_STATIC_VEC); self.more.push(value.into()); - } else if self.len < self.list.len() { - mem::replace( - self.list.get_mut(self.len).unwrap(), - mem::MaybeUninit::new(value.into()), - ); + } else if self.is_fixed_storage() { + self.set_into_list(self.len, value.into(), false); } else { self.more.push(value.into()); } self.len += 1; } /// Insert a new value to this `StaticVec` at a particular position. + /// + /// # Panics + /// + /// Panics if `index` is out of bounds. pub fn insert>(&mut self, index: usize, value: X) { if index > self.len { panic!("index OOB in StaticVec"); } - if self.len == self.list.len() { - // Move the fixed list to the Vec - self.more.extend( - self.list - .iter_mut() - .map(|v| mem::replace(v, mem::MaybeUninit::uninit())) - .map(Self::extract), - ); + if self.len == MAX_STATIC_VEC { + self.move_fixed_into_vec(MAX_STATIC_VEC); self.more.insert(index, value.into()); - } else if self.len < self.list.len() { + } else if self.is_fixed_storage() { + // Move all items one slot to the right for x in (index..self.len).rev() { - let temp = mem::replace(self.list.get_mut(x).unwrap(), mem::MaybeUninit::uninit()); - mem::replace(self.list.get_mut(x + 1).unwrap(), temp); + let orig_value = self.extract_from_list(x); + self.set_into_list(x + 1, orig_value, false); } - mem::replace( - self.list.get_mut(index).unwrap(), - mem::MaybeUninit::new(value.into()), - ); + self.set_into_list(index, value.into(), false); } else { self.more.insert(index, value.into()); } @@ -207,29 +293,62 @@ impl StaticVec { /// /// Panics if the `StaticVec` is empty. pub fn pop(&mut self) -> T { - if self.len <= 0 { + if self.is_empty() { panic!("nothing to pop!"); } - let result = if self.len <= self.list.len() { - Self::extract(mem::replace( - self.list.get_mut(self.len - 1).unwrap(), - mem::MaybeUninit::uninit(), - )) + let result = if self.is_fixed_storage() { + self.extract_from_list(self.len - 1) } else { - let r = self.more.pop().unwrap(); + let value = self.more.pop().unwrap(); // Move back to the fixed list - if self.more.len() == self.list.len() { - for index in (0..self.list.len()).rev() { - mem::replace( - self.list.get_mut(index).unwrap(), - mem::MaybeUninit::new(self.more.pop().unwrap()), - ); + if self.more.len() == MAX_STATIC_VEC { + for index in (0..MAX_STATIC_VEC).rev() { + let item = self.more.pop().unwrap(); + self.set_into_list(index, item, false); } } - r + value + }; + + self.len -= 1; + + result + } + /// Remove a value from this `StaticVec` at a particular position. + /// + /// # Panics + /// + /// Panics if `index` is out of bounds. + pub fn remove(&mut self, index: usize) -> T { + if index >= self.len { + panic!("index OOB in StaticVec"); + } + + let result = if self.is_fixed_storage() { + let value = self.extract_from_list(index); + + // Move all items one slot to the left + for x in index..self.len - 1 { + let orig_value = self.extract_from_list(x + 1); + self.set_into_list(x, orig_value, false); + } + + value + } else { + let value = self.more.remove(index); + + // Move back to the fixed list + if self.more.len() == MAX_STATIC_VEC { + for index in (0..MAX_STATIC_VEC).rev() { + let item = self.more.pop().unwrap(); + self.set_into_list(index, item, false); + } + } + + value }; self.len -= 1; @@ -248,15 +367,15 @@ impl StaticVec { /// /// # Panics /// - /// Panics if the index is out of bounds. - pub fn get_ref(&self, index: usize) -> &T { + /// Panics if `index` is out of bounds. + pub fn get(&self, index: usize) -> &T { if index >= self.len { panic!("index OOB in StaticVec"); } let list: &[T; MAX_STATIC_VEC] = unsafe { mem::transmute(&self.list) }; - if self.len <= list.len() { + if self.is_fixed_storage() { list.get(index).unwrap() } else { self.more.get(index).unwrap() @@ -266,7 +385,7 @@ impl StaticVec { /// /// # Panics /// - /// Panics if the index is out of bounds. + /// Panics if `index` is out of bounds. pub fn get_mut(&mut self, index: usize) -> &mut T { if index >= self.len { panic!("index OOB in StaticVec"); @@ -274,7 +393,7 @@ impl StaticVec { let list: &mut [T; MAX_STATIC_VEC] = unsafe { mem::transmute(&mut self.list) }; - if self.len <= list.len() { + if self.is_fixed_storage() { list.get_mut(index).unwrap() } else { self.more.get_mut(index).unwrap() @@ -284,7 +403,7 @@ impl StaticVec { pub fn iter(&self) -> impl Iterator { let list: &[T; MAX_STATIC_VEC] = unsafe { mem::transmute(&self.list) }; - if self.len <= list.len() { + if self.is_fixed_storage() { list[..self.len].iter() } else { self.more.iter() @@ -294,7 +413,7 @@ impl StaticVec { pub fn iter_mut(&mut self) -> impl Iterator { let list: &mut [T; MAX_STATIC_VEC] = unsafe { mem::transmute(&mut self.list) }; - if self.len <= list.len() { + if self.is_fixed_storage() { list[..self.len].iter_mut() } else { self.more.iter_mut() @@ -302,18 +421,66 @@ impl StaticVec { } } +impl StaticVec { + /// Get a mutable iterator to entries in the `StaticVec`. + pub fn into_iter(mut self) -> Box> { + if self.is_fixed_storage() { + let mut it = FixedStorageIterator { + data: unsafe { mem::MaybeUninit::uninit().assume_init() }, + index: 0, + limit: self.len, + }; + + for x in 0..self.len { + it.data[x] = mem::replace(self.list.get_mut(x).unwrap(), MaybeUninit::uninit()); + } + self.len = 0; + + Box::new(it) + } else { + Box::new(Vec::from(self).into_iter()) + } + } +} + +/// An iterator that takes control of the fixed-size storage of a `StaticVec` and returns its values. +struct FixedStorageIterator { + data: [MaybeUninit; MAX_STATIC_VEC], + index: usize, + limit: usize, +} + +impl Iterator for FixedStorageIterator { + type Item = T; + + fn next(&mut self) -> Option { + if self.index >= self.limit { + None + } else { + self.index += 1; + + let value = mem::replace( + self.data.get_mut(self.index - 1).unwrap(), + MaybeUninit::uninit(), + ); + + unsafe { Some(value.assume_init()) } + } + } +} + impl StaticVec { /// Get the item at a particular index, replacing it with the default. /// /// # Panics /// - /// Panics if the index is out of bounds. - pub fn get(&mut self, index: usize) -> T { + /// Panics if `index` is out of bounds. + pub fn take(&mut self, index: usize) -> T { if index >= self.len { panic!("index OOB in StaticVec"); } - mem::take(if self.len <= self.list.len() { + mem::take(if self.is_fixed_storage() { unsafe { mem::transmute(self.list.get_mut(index).unwrap()) } } else { self.more.get_mut(index).unwrap() @@ -333,7 +500,7 @@ impl AsRef<[T]> for StaticVec { fn as_ref(&self) -> &[T] { let list: &[T; MAX_STATIC_VEC] = unsafe { mem::transmute(&self.list) }; - if self.len <= list.len() { + if self.is_fixed_storage() { &list[..self.len] } else { &self.more[..] @@ -345,7 +512,7 @@ impl AsMut<[T]> for StaticVec { fn as_mut(&mut self) -> &mut [T] { let list: &mut [T; MAX_STATIC_VEC] = unsafe { mem::transmute(&mut self.list) }; - if self.len <= list.len() { + if self.is_fixed_storage() { &mut list[..self.len] } else { &mut self.more[..] @@ -353,20 +520,30 @@ impl AsMut<[T]> for StaticVec { } } +impl Index for StaticVec { + type Output = T; + + fn index(&self, index: usize) -> &Self::Output { + self.get(index) + } +} + +impl IndexMut for StaticVec { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + self.get_mut(index) + } +} + impl From> for Vec { fn from(mut value: StaticVec) -> Self { - if value.len <= value.list.len() { - value - .list - .iter_mut() - .map(|v| mem::replace(v, mem::MaybeUninit::uninit())) - .map(StaticVec::extract) - .collect() - } else { - let mut arr = Self::new(); - arr.append(&mut value.more); - arr + if value.len <= MAX_STATIC_VEC { + value.move_fixed_into_vec(value.len); } + value.len = 0; + + let mut arr = Self::new(); + arr.append(&mut value.more); + arr } } @@ -375,12 +552,9 @@ impl From> for StaticVec { let mut arr: Self = Default::default(); arr.len = value.len(); - if arr.len <= arr.list.len() { + if arr.len <= MAX_STATIC_VEC { for x in (0..arr.len).rev() { - mem::replace( - arr.list.get_mut(x).unwrap(), - mem::MaybeUninit::new(value.pop().unwrap()), - ); + arr.set_into_list(x, value.pop().unwrap(), false); } } else { arr.more = value; diff --git a/tests/modules.rs b/tests/modules.rs index 569cb4af..9c6c8ec4 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -84,28 +84,39 @@ fn test_module_resolver() -> Result<(), Box> { assert!(matches!( *engine - .eval::<()>( + .eval::( r#" + let x = 0; + for x in range(0, 10) { import "hello" as h; + x += h::answer; } + + x "# ) .expect_err("should error"), EvalAltResult::ErrorTooManyModules(_) )); + #[cfg(not(feature = "no_function"))] assert!(matches!( *engine - .eval::<()>( + .eval::( r#" + let x = 0; + fn foo() { import "hello" as h; + x += h::answer; } for x in range(0, 10) { foo(); } + + x "# ) .expect_err("should error"), @@ -114,6 +125,7 @@ fn test_module_resolver() -> Result<(), Box> { engine.set_max_modules(0); + #[cfg(not(feature = "no_function"))] engine.eval::<()>( r#" fn foo() { diff --git a/tests/operations.rs b/tests/operations.rs index 2637ef3b..c02e2381 100644 --- a/tests/operations.rs +++ b/tests/operations.rs @@ -43,20 +43,40 @@ fn test_max_operations_functions() -> Result<(), Box> { engine.eval::<()>( r#" + print("Test1"); + let x = 0; + + while x < 28 { + print(x); + x += 1; + } + "#, + )?; + + #[cfg(not(feature = "no_function"))] + engine.eval::<()>( + r#" + print("Test2"); fn inc(x) { x + 1 } let x = 0; while x < 20 { x = inc(x); } "#, )?; + #[cfg(not(feature = "no_function"))] assert!(matches!( *engine .eval::<()>( r#" + print("Test3"); fn inc(x) { x + 1 } let x = 0; - while x < 1000 { x = inc(x); } - "# + + while x < 28 { + print(x); + x = inc(x); + } + "#, ) .expect_err("should error"), EvalAltResult::ErrorTooManyOperations(_)