diff --git a/README.md b/README.md index 0339ae85..be949d40 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Rhai's current features set: * Easily [call a script-defined function](#calling-rhai-functions-from-rust) from Rust * Low compile-time overhead (~0.6 sec debug/~3 sec release for `rhai_runner` sample app) * Fairly efficient evaluation (1 million iterations in 0.75 sec on my 5 year old laptop) -* Relatively little `unsafe` code (yes there are some for performance reasons, and all `unsafe` code is limited to +* Relatively little `unsafe` code (yes there are some for performance reasons, and most `unsafe` code is limited to one single source file, all with names starting with `"unsafe_"`) * Sand-boxed (the scripting [`Engine`] can be declared immutable which cannot mutate the containing environment unless explicitly allowed via `RefCell` etc.) diff --git a/src/engine.rs b/src/engine.rs index 2c9a3a82..534a506a 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -8,7 +8,7 @@ use crate::module::Module; use crate::optimize::OptimizationLevel; use crate::packages::{CorePackage, Package, PackageLibrary, PackagesCollection, StandardPackage}; use crate::parser::{Expr, FnAccess, FnDef, ReturnType, SharedFnDef, Stmt, AST}; -use crate::r#unsafe::unsafe_cast_var_name; +use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; use crate::result::EvalAltResult; use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::token::Position; @@ -756,7 +756,8 @@ impl Engine { args.into_iter().map(|v| mem::take(*v)), ) .map(|(name, value)| { - let var_name = unsafe_cast_var_name(name.as_str(), &mut state); + let var_name = + unsafe_cast_var_name_to_lifetime(name.as_str(), &mut state); (var_name, ScopeEntryType::Normal, value) }), ); @@ -1172,11 +1173,10 @@ impl Engine { ) -> Result<(), Box> { match expr { Expr::FnCall(x) if x.1.is_none() => { - let mut arg_values = StaticVec::::new(); - - for arg_expr in x.3.iter() { - arg_values.push(self.eval_expr(scope, state, arg_expr, level)?); - } + let arg_values = + x.3.iter() + .map(|arg_expr| self.eval_expr(scope, state, arg_expr, level)) + .collect::, _>>()?; idx_values.push(Dynamic::from(arg_values)); } @@ -1471,14 +1471,10 @@ impl Engine { 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(); // Evaluate the text string as a script - let result = self.eval_script_expr( - scope, - state, - args.pop(), - args_expr[0].position(), - ); + let result = self.eval_script_expr(scope, state, args.pop(), pos); if scope.len() != prev_len { // IMPORTANT! If the eval defines new variables in the current scope, @@ -1709,7 +1705,7 @@ impl Engine { .or_else(|| self.packages.get_iter(tid)) { // Add the loop variable - let var_name = unsafe_cast_var_name(name, &state); + let var_name = unsafe_cast_var_name_to_lifetime(name, &state); scope.push(var_name, ()); let index = scope.len() - 1; state.scope_level += 1; @@ -1775,7 +1771,7 @@ impl Engine { Stmt::Let(x) if x.1.is_some() => { let ((var_name, pos), expr) = x.as_ref(); let val = self.eval_expr(scope, state, expr.as_ref().unwrap(), level)?; - let var_name = unsafe_cast_var_name(var_name, &state); + 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()) @@ -1783,7 +1779,7 @@ impl Engine { Stmt::Let(x) => { let ((var_name, pos), _) = x.as_ref(); - let var_name = unsafe_cast_var_name(var_name, &state); + let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state); scope.push(var_name, ()); self.inc_operations(state, *pos)?; Ok(Default::default()) @@ -1793,7 +1789,7 @@ impl Engine { Stmt::Const(x) if x.1.is_constant() => { let ((var_name, pos), expr) = x.as_ref(); let val = self.eval_expr(scope, state, &expr, level)?; - let var_name = unsafe_cast_var_name(var_name, &state); + 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()) @@ -1827,7 +1823,7 @@ impl Engine { let module = resolver.resolve(self, Scope::new(), &path, expr.position())?; - let mod_name = unsafe_cast_var_name(name, &state); + let mod_name = unsafe_cast_var_name_to_lifetime(name, &state); scope.push_module(mod_name, module); state.modules += 1; @@ -1848,7 +1844,7 @@ impl Engine { // Export statement Stmt::Export(list) => { - for ((id, id_pos), rename) in list.as_ref() { + for ((id, id_pos), rename) in list.iter() { // Mark scope variables as public if let Some(index) = scope .get_index(id) diff --git a/src/fn_call.rs b/src/fn_call.rs index e1da767b..58711ebb 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -9,7 +9,7 @@ use crate::utils::StaticVec; /// Any data type that can be converted into a `Vec` can be used /// as arguments to a function call. pub trait FuncArgs { - /// Convert to a `Vec` of the function call arguments. + /// Convert to a `StaticVec` of the function call arguments. fn into_vec(self) -> StaticVec; } diff --git a/src/lib.rs b/src/lib.rs index 203a6dfb..5c7a10ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -91,7 +91,6 @@ mod utils; pub use any::Dynamic; pub use engine::Engine; pub use error::{ParseError, ParseErrorType}; -pub use fn_call::FuncArgs; pub use fn_native::NativeCallable; pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn}; pub use module::Module; diff --git a/src/module.rs b/src/module.rs index f65428a0..056d1a75 100644 --- a/src/module.rs +++ b/src/module.rs @@ -716,7 +716,7 @@ impl Module { /// /// A `StaticVec` is used because most module-level access contains only one level, /// and it is wasteful to always allocate a `Vec` with one element. -#[derive(Clone, Eq, PartialEq, Hash, Default)] +#[derive(Clone, Eq, PartialEq, Default)] pub struct ModuleRef(StaticVec<(String, Position)>, Option); impl fmt::Debug for ModuleRef { diff --git a/src/optimize.rs b/src/optimize.rs index 04026059..f118fc3a 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -10,10 +10,12 @@ use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST}; use crate::result::EvalAltResult; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; use crate::token::Position; +use crate::utils::StaticVec; use crate::stdlib::{ boxed::Box, iter::empty, + mem, string::{String, ToString}, vec, vec::Vec, @@ -140,7 +142,11 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) - if preserve_result { // -> { expr, Noop } - Stmt::Block(Box::new((vec![Stmt::Expr(Box::new(expr)), x.1], pos))) + let mut statements = StaticVec::new(); + statements.push(Stmt::Expr(Box::new(expr))); + statements.push(x.1); + + Stmt::Block(Box::new((statements, pos))) } else { // -> expr Stmt::Expr(Box::new(expr)) @@ -193,7 +199,8 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) - Stmt::Break(pos) => { // Only a single break statement - turn into running the guard expression once state.set_dirty(); - let mut statements = vec![Stmt::Expr(Box::new(optimize_expr(expr, state)))]; + let mut statements = StaticVec::new(); + statements.push(Stmt::Expr(Box::new(optimize_expr(expr, state)))); if preserve_result { statements.push(Stmt::Noop(pos)) } @@ -229,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(x) => { + Stmt::Block(mut 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.into_iter() + x.0.iter_mut() .map(|stmt| match stmt { // Add constant into the state Stmt::Const(v) => { - let ((name, pos), expr) = *v; - state.push_constant(&name, expr); + let ((name, pos), expr) = v.as_mut(); + state.push_constant(name, mem::take(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(stmt, state, preserve_result), + _ => optimize_stmt(mem::take(stmt), state, preserve_result), }) .collect(); @@ -324,13 +331,13 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) - Stmt::Noop(pos) } // Only one let/import statement - leave it alone - [Stmt::Let(_)] | [Stmt::Import(_)] => Stmt::Block(Box::new((result, pos))), + [Stmt::Let(_)] | [Stmt::Import(_)] => Stmt::Block(Box::new((result.into(), pos))), // Only one statement - promote [_] => { state.set_dirty(); result.remove(0) } - _ => Stmt::Block(Box::new((result, pos))), + _ => Stmt::Block(Box::new((result.into(), pos))), } } // expr; @@ -392,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(m), Expr::Property(p)) if m.0.iter().all(|(_, x)| x.is_pure()) => { + (Expr::Map(mut 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.into_iter().find(|((name, _), _)| name == prop) - .map(|(_, expr)| expr.set_position(pos)) + m.0.iter_mut().find(|((name, _), _)| name == prop) + .map(|(_, expr)| mem::take(expr).set_position(pos)) .unwrap_or_else(|| Expr::Unit(pos)) } // lhs.rhs @@ -416,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.remove(i.0 as usize).set_position(a.1) + a.0.get(i.0 as usize).set_position(a.1) } // map[string] - (Expr::Map(m), Expr::StringConstant(s)) if m.0.iter().all(|(_, x)| x.is_pure()) => { + (Expr::Map(mut 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.into_iter().find(|((name, _), _)| name == &s.0) - .map(|(_, expr)| expr.set_position(pos)) + m.0.iter_mut().find(|((name, _), _)| name == &s.0) + .map(|(_, expr)| mem::take(expr).set_position(pos)) .unwrap_or_else(|| Expr::Unit(pos)) } // string[int] @@ -439,15 +446,13 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { }, // [ items .. ] #[cfg(not(feature = "no_index"))] - Expr::Array(a) => Expr::Array(Box::new((a.0 - .into_iter() - .map(|expr| optimize_expr(expr, state)) - .collect(), a.1))), + Expr::Array(mut a) => Expr::Array(Box::new((a.0 + .iter_mut().map(|expr| optimize_expr(mem::take(expr), state)) + .collect(), a.1))), // [ items .. ] #[cfg(not(feature = "no_object"))] - Expr::Map(m) => Expr::Map(Box::new((m.0 - .into_iter() - .map(|((key, pos), expr)| ((key, pos), optimize_expr(expr, state))) + 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))) .collect(), m.1))), // lhs in rhs Expr::In(x) => match (x.0, x.1) { @@ -527,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.into_iter().map(|a| optimize_expr(a, state)).collect(); + x.3 = x.3.iter_mut().map(|a| optimize_expr(mem::take(a), state)).collect(); Expr::FnCall(x) } @@ -542,7 +547,7 @@ 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.into_iter().map(|a| optimize_expr(a, state)).collect(); + x.3 = x.3.iter_mut().map(|a| optimize_expr(mem::take(a), state)).collect(); return Expr::FnCall(x); } @@ -574,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.into_iter().map(|a| optimize_expr(a, state)).collect(); + x.3 = x.3.iter_mut().map(|a| optimize_expr(mem::take(a), state)).collect(); Expr::FnCall(x) }) } // id(args ..) -> optimize function call arguments Expr::FnCall(mut x) => { - x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect(); + x.3 = x.3.iter_mut().map(|a| optimize_expr(mem::take(a), state)).collect(); Expr::FnCall(x) } diff --git a/src/packages/mod.rs b/src/packages/mod.rs index 7c146f7f..e56843f0 100644 --- a/src/packages/mod.rs +++ b/src/packages/mod.rs @@ -2,6 +2,7 @@ use crate::fn_native::{NativeCallable, SharedIteratorFunction}; use crate::module::Module; +use crate::utils::StaticVec; use crate::stdlib::{any::TypeId, boxed::Box, collections::HashMap, rc::Rc, sync::Arc, vec::Vec}; @@ -54,7 +55,7 @@ pub type PackageLibrary = Arc; #[derive(Clone, Default)] pub(crate) struct PackagesCollection { /// Collection of `PackageLibrary` instances. - packages: Vec, + packages: StaticVec, } impl PackagesCollection { diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index eb6208f1..2c90bea5 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -1,6 +1,7 @@ use crate::def_package; use crate::module::FuncReturn; use crate::parser::INT; +use crate::utils::StaticVec; #[cfg(not(feature = "no_index"))] use crate::engine::Array; @@ -29,7 +30,7 @@ fn sub_string(s: &mut String, start: INT, len: INT) -> FuncReturn { start as usize }; - let chars: Vec<_> = s.chars().collect(); + let chars: StaticVec<_> = s.chars().collect(); let len = if offset + (len as usize) > chars.len() { chars.len() - offset @@ -37,7 +38,7 @@ fn sub_string(s: &mut String, start: INT, len: INT) -> FuncReturn { len as usize }; - Ok(chars[offset..][..len].into_iter().collect()) + Ok(chars.iter().skip(offset).take(len).cloned().collect()) } fn crop_string(s: &mut String, start: INT, len: INT) -> FuncReturn<()> { let offset = if s.is_empty() || len <= 0 { @@ -52,7 +53,7 @@ fn crop_string(s: &mut String, start: INT, len: INT) -> FuncReturn<()> { start as usize }; - let chars: Vec<_> = s.chars().collect(); + let chars: StaticVec<_> = s.chars().collect(); let len = if offset + (len as usize) > chars.len() { chars.len() - offset @@ -62,8 +63,10 @@ fn crop_string(s: &mut String, start: INT, len: INT) -> FuncReturn<()> { s.clear(); - chars[offset..][..len] - .into_iter() + chars + .iter() + .skip(offset) + .take(len) .for_each(|&ch| s.push(ch)); Ok(()) @@ -189,9 +192,9 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str "truncate", |s: &mut String, len: INT| { if len >= 0 { - let chars: Vec<_> = s.chars().take(len as usize).collect(); + let chars: StaticVec<_> = s.chars().take(len as usize).collect(); s.clear(); - chars.into_iter().for_each(|ch| s.push(ch)); + chars.iter().for_each(|&ch| s.push(ch)); } else { s.clear(); } diff --git a/src/parser.rs b/src/parser.rs index 0fefc84c..9e608b79 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -7,7 +7,7 @@ use crate::error::{LexError, ParseError, ParseErrorType}; use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::token::{Position, Token, TokenIterator}; -use crate::utils::EMPTY_TYPE_ID; +use crate::utils::{StaticVec, EMPTY_TYPE_ID}; #[cfg(not(feature = "no_module"))] use crate::module::ModuleRef; @@ -195,7 +195,7 @@ pub struct FnDef { /// Function access mode. pub access: FnAccess, /// Names of function parameters. - pub params: Vec, + pub params: StaticVec, /// Function body. pub body: Stmt, /// Position of the function definition. @@ -294,7 +294,7 @@ pub enum Stmt { /// const id = expr Const(Box<((String, Position), Expr)>), /// { stmt; ... } - Block(Box<(Vec, Position)>), + Block(Box<(StaticVec, Position)>), /// { stmt } Expr(Box), /// continue @@ -306,7 +306,13 @@ pub enum Stmt { /// import expr as module Import(Box<(Expr, (String, Position))>), /// expr id as name, ... - Export(Box)>>), + Export(Box)>>), +} + +impl Default for Stmt { + fn default() -> Self { + Self::Noop(Default::default()) + } } impl Stmt { @@ -324,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(0).unwrap().0).1, + Stmt::Export(x) => (x.get_ref(0).0).1, } } @@ -406,7 +412,7 @@ pub enum Expr { (Cow<'static, str>, Position), MRef, u64, - Vec, + StaticVec, Option, )>, ), @@ -417,9 +423,9 @@ pub enum Expr { /// expr[expr] Index(Box<(Expr, Expr, Position)>), /// [ expr, ... ] - Array(Box<(Vec, Position)>), + Array(Box<(StaticVec, Position)>), /// #{ name:expr, ... } - Map(Box<(Vec<((String, Position), Expr)>, Position)>), + Map(Box<(StaticVec<((String, Position), Expr)>, Position)>), /// lhs in rhs In(Box<(Expr, Expr, Position)>), /// lhs && rhs @@ -434,6 +440,12 @@ pub enum Expr { Unit(Position), } +impl Default for Expr { + fn default() -> Self { + Self::Unit(Default::default()) + } +} + impl Expr { /// Get the `Dynamic` value of a constant expression. /// @@ -713,7 +725,7 @@ fn parse_call_expr<'a>( begin: Position, allow_stmt_expr: bool, ) -> Result> { - let mut args = Vec::new(); + let mut args = StaticVec::new(); match input.peek().unwrap() { // id @@ -1013,7 +1025,7 @@ fn parse_array_literal<'a>( pos: Position, allow_stmt_expr: bool, ) -> Result> { - let mut arr = Vec::new(); + let mut arr = StaticVec::new(); if !match_token(input, Token::RightBracket)? { while !input.peek().unwrap().0.is_eof() { @@ -1056,7 +1068,7 @@ fn parse_map_literal<'a>( pos: Position, allow_stmt_expr: bool, ) -> Result> { - let mut map = Vec::new(); + let mut map = StaticVec::new(); if !match_token(input, Token::RightBrace)? { while !input.peek().unwrap().0.is_eof() { @@ -1296,15 +1308,17 @@ fn parse_unary<'a>( Expr::FloatConstant(x) => Ok(Expr::FloatConstant(Box::new((-x.0, x.1)))), // Call negative function - e => { + expr => { let op = "-"; let hash = calc_fn_hash(empty(), op, repeat(EMPTY_TYPE_ID()).take(2)); + let mut args = StaticVec::new(); + args.push(expr); Ok(Expr::FnCall(Box::new(( (op.into(), pos), None, hash, - vec![e], + args, None, )))) } @@ -1318,7 +1332,8 @@ fn parse_unary<'a>( // !expr (Token::Bang, _) => { let pos = eat_token(input, Token::Bang); - let expr = vec![parse_primary(input, stack, allow_stmt_expr)?]; + let mut args = StaticVec::new(); + args.push(parse_primary(input, stack, allow_stmt_expr)?); let op = "!"; let hash = calc_fn_hash(empty(), op, repeat(EMPTY_TYPE_ID()).take(2)); @@ -1327,7 +1342,7 @@ fn parse_unary<'a>( (op.into(), pos), None, hash, - expr, + args, Some(false.into()), // NOT operator, when operating on invalid operand, defaults to false )))) } @@ -1412,9 +1427,13 @@ fn parse_op_assignment_stmt<'a>( let rhs = parse_expr(input, stack, allow_stmt_expr)?; // lhs op= rhs -> lhs = op(lhs, rhs) - let args = vec![lhs_copy, rhs]; + let mut args = StaticVec::new(); + args.push(lhs_copy); + args.push(rhs); + let hash = calc_fn_hash(empty(), op, repeat(EMPTY_TYPE_ID()).take(args.len())); let rhs_expr = Expr::FnCall(Box::new(((op.into(), pos), None, hash, args, None))); + make_assignment_stmt(stack, lhs, rhs_expr, pos) } @@ -1695,7 +1714,10 @@ fn parse_binary_op<'a>( let cmp_def = Some(false.into()); let op = op_token.syntax(); let hash = calc_fn_hash(empty(), &op, repeat(EMPTY_TYPE_ID()).take(2)); - let mut args = vec![current_lhs, rhs]; + + let mut args = StaticVec::new(); + args.push(current_lhs); + args.push(rhs); current_lhs = match op_token { Token::Plus => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))), @@ -1721,13 +1743,13 @@ fn parse_binary_op<'a>( } Token::Or => { - let rhs = args.pop().unwrap(); - let current_lhs = args.pop().unwrap(); + let rhs = args.pop(); + let current_lhs = args.pop(); Expr::Or(Box::new((current_lhs, rhs, pos))) } Token::And => { - let rhs = args.pop().unwrap(); - let current_lhs = args.pop().unwrap(); + let rhs = args.pop(); + let current_lhs = args.pop(); Expr::And(Box::new((current_lhs, rhs, pos))) } Token::Ampersand => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))), @@ -1735,15 +1757,15 @@ fn parse_binary_op<'a>( Token::XOr => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))), Token::In => { - let rhs = args.pop().unwrap(); - let current_lhs = args.pop().unwrap(); + let rhs = args.pop(); + let current_lhs = args.pop(); make_in_expr(current_lhs, rhs, pos)? } #[cfg(not(feature = "no_object"))] Token::Period => { - let mut rhs = args.pop().unwrap(); - let current_lhs = args.pop().unwrap(); + let mut rhs = args.pop(); + let current_lhs = args.pop(); match &mut rhs { // current_lhs.rhs(...) - method call @@ -2025,7 +2047,7 @@ fn parse_import<'a>( fn parse_export<'a>(input: &mut Peekable>) -> Result> { eat_token(input, Token::Export); - let mut exports = Vec::new(); + let mut exports = StaticVec::new(); loop { let (id, id_pos) = match input.next().unwrap() { @@ -2098,7 +2120,7 @@ fn parse_block<'a>( } }; - let mut statements = Vec::new(); + let mut statements = StaticVec::new(); let prev_len = stack.len(); while !match_token(input, Token::RightBrace)? { diff --git a/src/unsafe.rs b/src/unsafe.rs index 64ba8904..efbf80c7 100644 --- a/src/unsafe.rs +++ b/src/unsafe.rs @@ -47,9 +47,9 @@ pub fn unsafe_cast_box(item: Box) -> Result, B /// current `Scope` without cloning the variable name. Doing this is safe because all local /// variables in the `Scope` are cleared out before existing the block. /// -/// Force-casting a local variable lifetime to the current `Scope`'s larger lifetime saves +/// Force-casting a local variable's lifetime to the current `Scope`'s larger lifetime saves /// on allocations and string cloning, thus avoids us having to maintain a chain of `Scope`'s. -pub fn unsafe_cast_var_name<'s>(name: &str, state: &State) -> Cow<'s, str> { +pub fn unsafe_cast_var_name_to_lifetime<'s>(name: &str, state: &State) -> Cow<'s, str> { // If not at global level, we can force-cast if state.scope_level > 0 { // WARNING - force-cast the variable name into the scope's lifetime to avoid cloning it @@ -62,7 +62,7 @@ pub fn unsafe_cast_var_name<'s>(name: &str, state: &State) -> Cow<'s, str> { } } -/// Provide a type instance that is memory-zeroed. -pub fn unsafe_zeroed() -> T { - unsafe { mem::MaybeUninit::zeroed().assume_init() } +/// 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 f61d24ea..e7e62eeb 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,10 @@ //! Module containing various utility types and functions. +//! +//! # Safety +//! +//! The `StaticVec` type has some `unsafe` blocks. -use crate::r#unsafe::unsafe_zeroed; +use crate::r#unsafe::unsafe_uninit; use crate::stdlib::{ any::TypeId, @@ -8,6 +12,7 @@ use crate::stdlib::{ hash::{Hash, Hasher}, iter::FromIterator, mem, + ops::Drop, vec::Vec, }; @@ -47,6 +52,8 @@ 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`. /// /// This is essentially a knock-off of the [`staticvec`](https://crates.io/crates/staticvec) crate. @@ -54,22 +61,57 @@ pub fn calc_fn_spec<'a>( /// /// # Safety /// -/// This type uses some unsafe code (mainly to zero out unused array slots) for efficiency. +/// This type uses some unsafe code (mainly for uninitialized/unused array slots) for efficiency. // // TODO - remove unsafe code -#[derive(Clone, Hash)] pub struct StaticVec { /// Total number of values held. len: usize, - /// Static storage. 4 slots should be enough for most cases - i.e. four levels of indirection. - list: [T; 4], + /// 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], /// Dynamic storage. For spill-overs. more: Vec, } +impl Drop for StaticVec { + fn drop(&mut self) { + self.clear(); + } +} + +impl Default for StaticVec { + fn default() -> Self { + Self { + len: 0, + list: unsafe_uninit(), + more: Vec::new(), + } + } +} + impl PartialEq for StaticVec { fn eq(&self, other: &Self) -> bool { - self.len == other.len && self.list == other.list && self.more == other.more + self.len == other.len + //&& self.list[0..self.len] == other.list[0..self.len] + && self.more == other.more + } +} + +impl Clone for StaticVec { + fn clone(&self) -> Self { + let mut value: Self = Default::default(); + value.len = self.len; + + if self.len <= self.list.len() { + 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()); + } + } else { + value.more = self.more.clone(); + } + + value } } @@ -87,35 +129,75 @@ impl FromIterator for StaticVec { } } -impl Default for StaticVec { - fn default() -> Self { - Self { - len: 0, - list: unsafe_zeroed(), - more: Vec::new(), - } - } -} - 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() { + for x in 0..self.len { + Self::extract(mem::replace( + self.list.get_mut(x).unwrap(), + mem::MaybeUninit::uninit(), + )); + } + } else { + self.more.clear(); + } + self.len = 0; + } /// 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 - for x in 0..self.list.len() { - let def_val: T = unsafe_zeroed(); - self.more - .push(mem::replace(self.list.get_mut(x).unwrap(), def_val)); - } - self.more.push(value.into()); - } else if self.len > self.list.len() { + self.more.extend( + self.list + .iter_mut() + .map(|v| mem::replace(v, mem::MaybeUninit::uninit())) + .map(Self::extract), + ); 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 { - self.list[self.len] = value.into(); + self.more.push(value.into()); + } + self.len += 1; + } + /// Insert a new value to this `StaticVec` at a particular position. + 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), + ); + self.more.insert(index, value.into()); + } else if self.len < self.list.len() { + 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); + } + mem::replace( + self.list.get_mut(index).unwrap(), + mem::MaybeUninit::new(value.into()), + ); + } else { + self.more.insert(index, value.into()); } self.len += 1; } @@ -125,18 +207,25 @@ impl StaticVec { /// /// Panics if the `StaticVec` is empty. pub fn pop(&mut self) -> T { - let result = if self.len <= 0 { - panic!("nothing to pop!") - } else if self.len <= self.list.len() { - let def_val: T = unsafe_zeroed(); - mem::replace(self.list.get_mut(self.len - 1).unwrap(), def_val) + if self.len <= 0 { + 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(), + )) } else { let r = self.more.pop().unwrap(); // Move back to the fixed list if self.more.len() == self.list.len() { - for x in 0..self.list.len() { - self.list[self.list.len() - 1 - x] = self.more.pop().unwrap(); + for index in (0..self.list.len()).rev() { + mem::replace( + self.list.get_mut(index).unwrap(), + mem::MaybeUninit::new(self.more.pop().unwrap()), + ); } } @@ -151,6 +240,10 @@ impl StaticVec { pub fn len(&self) -> usize { self.len } + /// Is this `StaticVec` empty? + pub fn is_empty(&self) -> bool { + self.len == 0 + } /// Get a reference to the item at a particular index. /// /// # Panics @@ -161,8 +254,10 @@ impl StaticVec { panic!("index OOB in StaticVec"); } - if self.len < self.list.len() { - self.list.get(index).unwrap() + let list: &[T; MAX_STATIC_VEC] = unsafe { mem::transmute(&self.list) }; + + if self.len <= list.len() { + list.get(index).unwrap() } else { self.more.get(index).unwrap() } @@ -177,46 +272,52 @@ impl StaticVec { panic!("index OOB in StaticVec"); } - if self.len < self.list.len() { - self.list.get_mut(index).unwrap() + let list: &mut [T; MAX_STATIC_VEC] = unsafe { mem::transmute(&mut self.list) }; + + if self.len <= list.len() { + list.get_mut(index).unwrap() } else { self.more.get_mut(index).unwrap() } } /// Get an iterator to entries in the `StaticVec`. pub fn iter(&self) -> impl Iterator { - if self.len > self.list.len() { - self.more.iter() + let list: &[T; MAX_STATIC_VEC] = unsafe { mem::transmute(&self.list) }; + + if self.len <= list.len() { + list[..self.len].iter() } else { - self.list[..self.len].iter() + self.more.iter() } } /// Get a mutable iterator to entries in the `StaticVec`. pub fn iter_mut(&mut self) -> impl Iterator { - if self.len > self.list.len() { - self.more.iter_mut() + let list: &mut [T; MAX_STATIC_VEC] = unsafe { mem::transmute(&mut self.list) }; + + if self.len <= list.len() { + list[..self.len].iter_mut() } else { - self.list[..self.len].iter_mut() + self.more.iter_mut() } } } -impl StaticVec { - /// Get the item at a particular index. +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(&self, index: usize) -> T { + pub fn get(&mut self, index: usize) -> T { if index >= self.len { panic!("index OOB in StaticVec"); } - if self.len < self.list.len() { - *self.list.get(index).unwrap() + mem::take(if self.len <= self.list.len() { + unsafe { mem::transmute(self.list.get_mut(index).unwrap()) } } else { - *self.more.get(index).unwrap() - } + self.more.get_mut(index).unwrap() + }) } } @@ -230,20 +331,61 @@ impl fmt::Debug for StaticVec { impl AsRef<[T]> for StaticVec { fn as_ref(&self) -> &[T] { - if self.len > self.list.len() { - &self.more[..] + let list: &[T; MAX_STATIC_VEC] = unsafe { mem::transmute(&self.list) }; + + if self.len <= list.len() { + &list[..self.len] } else { - &self.list[..self.len] + &self.more[..] } } } impl AsMut<[T]> for StaticVec { fn as_mut(&mut self) -> &mut [T] { - if self.len > self.list.len() { - &mut self.more[..] + let list: &mut [T; MAX_STATIC_VEC] = unsafe { mem::transmute(&mut self.list) }; + + if self.len <= list.len() { + &mut list[..self.len] } else { - &mut self.list[..self.len] + &mut self.more[..] } } } + +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 + } + } +} + +impl From> for StaticVec { + fn from(mut value: Vec) -> Self { + let mut arr: Self = Default::default(); + arr.len = value.len(); + + if arr.len <= arr.list.len() { + for x in (0..arr.len).rev() { + mem::replace( + arr.list.get_mut(x).unwrap(), + mem::MaybeUninit::new(value.pop().unwrap()), + ); + } + } else { + arr.more = value; + } + + arr + } +}