From 32f41c69bdb57c042fbdf72c76d73d408276bcd7 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 3 Nov 2020 21:50:14 +0800 Subject: [PATCH 01/10] Refine code and docs. --- RELEASES.md | 6 ++++ doc/src/safety/progress.md | 16 +++++++++- doc/src/start/builds/performance.md | 5 +-- doc/src/start/builds/wasm.md | 13 ++++---- doc/src/start/features.md | 2 +- src/fn_native.rs | 5 ++- src/fn_register.rs | 2 +- src/module/resolvers/stat.rs | 2 +- src/scope.rs | 24 +++++++-------- src/serde_impl/de.rs | 47 +++++++++++------------------ tests/plugins.rs | 2 +- 11 files changed, 67 insertions(+), 57 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index af4262ba..9c654cdd 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -19,6 +19,10 @@ Breaking changes ---------------- * Custom syntax can no longer start with a keyword (even a _reserved_ one), even if it has been disabled. That is to avoid breaking scripts later when the keyword is no longer disabled. + +Changes to Error Handling +------------------------ + * `EvalAltResult::ErrorAssignmentToUnknownLHS` is moved to `ParseError::AssignmentToInvalidLHS`. `ParseError::AssignmentToCopy` is removed. * `EvalAltResult::ErrorDataTooLarge` is simplified. * `Engine::on_progress` closure signature now returns `Option` with the termination value passed on to `EvalAltResult::ErrorTerminated`. @@ -30,11 +34,13 @@ New features * `f32_float` feature to set `FLOAT` to `f32`. * Low-level API for custom syntax allowing more flexibility in designing the syntax. * `Module::fill_with` to poly-fill a module with another. +* Scripts terminated via `Engine::on_progress` can now pass on a value as a termination token. Enhancements ------------ * Essential AST structures like `Expr` and `Stmt` are packed into smaller sizes (16 bytes and 32 bytes on 64-bit), stored inline for more cache friendliness, and de-`Box`ed as much as possible. +* `Scope` is optimized for cache friendliness. Version 0.19.3 diff --git a/doc/src/safety/progress.md b/doc/src/safety/progress.md index 99158470..39e274df 100644 --- a/doc/src/safety/progress.md +++ b/doc/src/safety/progress.md @@ -24,7 +24,21 @@ engine.on_progress(|&count| { // parameter is '&u64' - number of operations al The closure passed to `Engine::on_progress` will be called once for every operation. Return `Some(token)` to terminate the script immediately, with the provided value -(any [`Dynamic`] value) passed to `EvalAltResult::ErrorTerminated` as a termination token. +(any [`Dynamic`]) acting as a termination token. + + +Termination Token +----------------- + +The [`Dynamic`] value returned by the closure for `Engine::on_progress` is a _termination token_. +A script that is manually terminated returns with `Err(EvalAltResult::ErrorTerminated)` +wrapping this value. + +The termination token is commonly used to provide information on the _reason_ or _source_ +behind the termination decision. + +If the termination token is not needed, simply return `Some(().into())` to terminate the script +run with [`()`] as the token. Operations Count vs. Progress Percentage diff --git a/doc/src/start/builds/performance.md b/doc/src/start/builds/performance.md index 78ca762e..35ee122b 100644 --- a/doc/src/start/builds/performance.md +++ b/doc/src/start/builds/performance.md @@ -29,8 +29,9 @@ due to 64-bit arithmetic requiring more CPU cycles to complete. Minimize Size of `Dynamic` ------------------------- -Turning on [`no_float`] and [`only_i32`] on 32-bit targets makes the critical [`Dynamic`] data type only 8 bytes long. -Normally [`Dynamic`] can be up to 16 bytes (e.g. on x86/x64 CPU's) in order to hold an `i64` or `f64`. +Turning on [`no_float`] or [`f32_float`] and [`only_i32`] on 32-bit targets makes the critical [`Dynamic`] +data type only 8 bytes long. +Normally [`Dynamic`] can be up to 12-16 bytes in order to hold an `i64` or `f64`. A small [`Dynamic`] helps performance due to better cache efficiency. diff --git a/doc/src/start/builds/wasm.md b/doc/src/start/builds/wasm.md index fbbc9007..0875a778 100644 --- a/doc/src/start/builds/wasm.md +++ b/doc/src/start/builds/wasm.md @@ -42,13 +42,14 @@ are typically used for a WASM build: | Feature | Description | | :-----------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`unchecked`] | When a WASM module panics, it doesn't crash the entire web app; however this also disables [maximum number of operations] and [progress] tracking so a script can still run indefinitely - the web app must terminate it itself. | -| [`only_i32`] | JavaScript has only one `number` type and we're only supporting `wasm32` here (so far). | +| [`only_i32`] | WASM supports 32-bit and 64-bit integers, but most scripts will only need 32-bit. | +| [`f32_float`] | WASM supports 32-bit single-precision and 64-bit double-precision floating-point numbers, but single-precision is usually fine for most uses. | | [`no_module`] | A WASM module cannot load modules from the file system, so usually this is not needed, but the savings are minimal; alternatively, a custom [module resolver] can be provided that loads other Rhai scripts. | The following features are typically _not_ used because they don't make sense in a WASM build: -| Feature | Why unnecessary | -| :-----------: | ------------------------------------------------------------------ | -| [`sync`] | WASM is single-threaded. | -| [`no_std`] | `std` lib works fine with WASM. | -| [`internals`] | WASM usually doesn't need to access Rhai internal data structures. | +| Feature | Why unnecessary | +| :-----------: | ------------------------------------------------------------------------------------------------------ | +| [`sync`] | WASM is single-threaded. | +| [`no_std`] | `std` lib works fine with WASM. | +| [`internals`] | WASM usually doesn't need to access Rhai internal data structures, unless you are walking the [`AST`]. | diff --git a/doc/src/start/features.md b/doc/src/start/features.md index 60508ed4..85a13168 100644 --- a/doc/src/start/features.md +++ b/doc/src/start/features.md @@ -17,7 +17,7 @@ more control over what a script can (or cannot) do. | `sync` | no | restricts all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and [`AST`], are all `Send + Sync` | | `no_optimize` | no | disables [script optimization] | | `no_float` | no | disables floating-point numbers and math | -| `f32_float` | no | sets the system floating-point type to `f32` instead of `f64` | +| `f32_float` | no | sets the system floating-point type to `f32` instead of `f64`. `FLOAT` is set to `f32` | | `only_i32` | no | sets the system integer type to `i32` and disable all other integer types. `INT` is set to `i32` | | `only_i64` | no | sets the system integer type to `i64` and disable all other integer types. `INT` is set to `i64` | | `no_index` | no | disables [arrays] and indexing features | diff --git a/src/fn_native.rs b/src/fn_native.rs index 56488ac2..ef30149c 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -115,8 +115,8 @@ pub struct FnPtr(ImmutableString, StaticVec); impl FnPtr { /// Create a new function pointer. #[inline(always)] - pub(crate) fn new_unchecked>( - name: S, + pub(crate) fn new_unchecked( + name: impl Into, curry: StaticVec, ) -> Self { Self(name.into(), curry) @@ -147,7 +147,6 @@ impl FnPtr { pub fn is_anonymous(&self) -> bool { self.0.starts_with(FN_ANONYMOUS) } - /// Call the function pointer with curried arguments (if any). /// /// If this function is a script-defined function, it must not be marked private. diff --git a/src/fn_register.rs b/src/fn_register.rs index 4a8ac472..f1e1f50e 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -144,7 +144,7 @@ macro_rules! make_func { /// To Dynamic mapping function. #[inline(always)] -pub fn map_dynamic(data: T) -> Result> { +pub fn map_dynamic(data: impl Variant + Clone) -> Result> { Ok(data.into_dynamic()) } diff --git a/src/module/resolvers/stat.rs b/src/module/resolvers/stat.rs index 4b64ad91..5d895e9d 100644 --- a/src/module/resolvers/stat.rs +++ b/src/module/resolvers/stat.rs @@ -48,7 +48,7 @@ impl StaticModuleResolver { } /// Add a module keyed by its path. #[inline(always)] - pub fn insert>(&mut self, path: S, module: Module) { + pub fn insert(&mut self, path: impl Into, module: Module) { self.0.insert(path.into(), module); } /// Remove a module given its path. diff --git a/src/scope.rs b/src/scope.rs index 970ecba0..1f2befb3 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -168,10 +168,10 @@ impl<'a> Scope<'a> { /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` #[inline(always)] - pub fn push>, T: Variant + Clone>( + pub fn push( &mut self, - name: K, - value: T, + name: impl Into>, + value: impl Variant + Clone, ) -> &mut Self { self.push_dynamic_value(name, EntryType::Normal, Dynamic::from(value)) } @@ -189,7 +189,7 @@ impl<'a> Scope<'a> { /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` #[inline(always)] - pub fn push_dynamic>>(&mut self, name: K, value: Dynamic) -> &mut Self { + pub fn push_dynamic(&mut self, name: impl Into>, value: Dynamic) -> &mut Self { self.push_dynamic_value(name, EntryType::Normal, value) } @@ -212,10 +212,10 @@ impl<'a> Scope<'a> { /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` #[inline(always)] - pub fn push_constant>, T: Variant + Clone>( + pub fn push_constant( &mut self, - name: K, - value: T, + name: impl Into>, + value: impl Variant + Clone, ) -> &mut Self { self.push_dynamic_value(name, EntryType::Constant, Dynamic::from(value)) } @@ -240,9 +240,9 @@ impl<'a> Scope<'a> { /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` #[inline(always)] - pub fn push_constant_dynamic>>( + pub fn push_constant_dynamic( &mut self, - name: K, + name: impl Into>, value: Dynamic, ) -> &mut Self { self.push_dynamic_value(name, EntryType::Constant, value) @@ -250,9 +250,9 @@ impl<'a> Scope<'a> { /// Add (push) a new entry with a `Dynamic` value to the Scope. #[inline] - pub(crate) fn push_dynamic_value>>( + pub(crate) fn push_dynamic_value( &mut self, - name: K, + name: impl Into>, entry_type: EntryType, value: Dynamic, ) -> &mut Self { @@ -377,7 +377,7 @@ impl<'a> Scope<'a> { /// assert_eq!(my_scope.get_value::("x").unwrap(), 0); /// ``` #[inline(always)] - pub fn set_value(&mut self, name: &'a str, value: T) -> &mut Self { + pub fn set_value(&mut self, name: &'a str, value: impl Variant + Clone) -> &mut Self { match self.get_index(name) { None => { self.push(name, value); diff --git a/src/serde_impl/de.rs b/src/serde_impl/de.rs index be0a5633..c040b191 100644 --- a/src/serde_impl/de.rs +++ b/src/serde_impl/de.rs @@ -452,28 +452,19 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { } /// `SeqAccess` implementation for arrays. -struct IterateArray<'a, ITER> -where - ITER: Iterator, -{ +struct IterateArray<'a, ITER: Iterator> { /// Iterator for a stream of `Dynamic` values. iter: ITER, } #[cfg(not(feature = "no_index"))] -impl<'a, ITER> IterateArray<'a, ITER> -where - ITER: Iterator, -{ +impl<'a, ITER: Iterator> IterateArray<'a, ITER> { pub fn new(iter: ITER) -> Self { Self { iter } } } -impl<'a: 'de, 'de, ITER> SeqAccess<'de> for IterateArray<'a, ITER> -where - ITER: Iterator, -{ +impl<'a: 'de, 'de, ITER: Iterator> SeqAccess<'de> for IterateArray<'a, ITER> { type Error = Box; fn next_element_seed>( @@ -555,10 +546,10 @@ impl<'t, 'de> EnumAccess<'de> for EnumDeserializer<'t, 'de> { type Error = Box; type Variant = Self; - fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> - where - V: DeserializeSeed<'de>, - { + fn variant_seed>( + self, + seed: V, + ) -> Result<(V::Value, Self::Variant), Self::Error> { seed.deserialize(self.tag.into_deserializer()) .map(|v| (v, self)) } @@ -572,28 +563,26 @@ impl<'t, 'de> VariantAccess<'de> for EnumDeserializer<'t, 'de> { Deserialize::deserialize(&mut self.content) } - fn newtype_variant_seed(mut self, seed: T) -> Result - where - T: DeserializeSeed<'de>, - { + fn newtype_variant_seed>( + mut self, + seed: T, + ) -> Result { seed.deserialize(&mut self.content) } - fn tuple_variant(mut self, len: usize, visitor: V) -> Result - where - V: Visitor<'de>, - { + fn tuple_variant>( + mut self, + len: usize, + visitor: V, + ) -> Result { self.content.deserialize_tuple(len, visitor) } - fn struct_variant( + fn struct_variant>( mut self, fields: &'static [&'static str], visitor: V, - ) -> Result - where - V: Visitor<'de>, - { + ) -> Result { self.content.deserialize_struct("", fields, visitor) } } diff --git a/tests/plugins.rs b/tests/plugins.rs index ce36d6bd..84da570c 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -66,7 +66,7 @@ macro_rules! reg_functions { } } -fn make_greeting(n: T) -> String { +fn make_greeting(n: impl std::fmt::Display) -> String { format!("{} kitties", n) } From 2168fd536111263d9dd6000e85a6ad1ca98b25b5 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 4 Nov 2020 11:49:02 +0800 Subject: [PATCH 02/10] Expr::Stmt takes a statements block. --- src/ast.rs | 4 ++-- src/engine.rs | 53 +++++++++++++++++++++++++++++++---------------- src/engine_api.rs | 4 ++-- src/fn_call.rs | 7 ++++--- src/optimize.rs | 24 ++++++++++++++++----- src/parser.rs | 32 +++++++++++++++------------- 6 files changed, 80 insertions(+), 44 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index c13c2b8b..ebf38250 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -908,7 +908,7 @@ pub enum Expr { /// Property access - (getter, setter), prop Property(Box<((String, String), IdentX)>), /// { stmt } - Stmt(Box, Position), + Stmt(Box>, Position), /// Wrapped expression - should not be optimized away. Expr(Box), /// func(expr, ... ) @@ -1092,7 +1092,7 @@ impl Expr { x.lhs.is_pure() && x.rhs.is_pure() } - Self::Stmt(x, _) => x.is_pure(), + Self::Stmt(x, _) => x.iter().all(Stmt::is_pure), Self::Variable(_) => true, diff --git a/src/engine.rs b/src/engine.rs index 9792c766..bb1f0725 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1579,7 +1579,9 @@ impl Engine { Expr::Property(_) => unreachable!(), // Statement block - Expr::Stmt(x, _) => self.eval_stmt(scope, mods, state, lib, this_ptr, x, level), + Expr::Stmt(x, _) => { + self.eval_statements(scope, mods, state, lib, this_ptr, x.as_ref(), level) + } // lhs[idx_expr] #[cfg(not(feature = "no_index"))] @@ -1706,6 +1708,37 @@ impl Engine { .map_err(|err| err.fill_position(expr.position())) } + pub(crate) fn eval_statements<'a>( + &self, + scope: &mut Scope, + mods: &mut Imports, + state: &mut State, + lib: &[&Module], + this_ptr: &mut Option<&mut Dynamic>, + statements: impl IntoIterator, + level: usize, + ) -> Result> { + let prev_scope_len = scope.len(); + let prev_mods_len = mods.len(); + state.scope_level += 1; + + let result = statements + .into_iter() + .try_fold(Default::default(), |_, stmt| { + self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level) + }); + + scope.rewind(prev_scope_len); + mods.truncate(prev_mods_len); + state.scope_level -= 1; + + // The impact of an eval statement goes away at the end of a block + // because any new variables introduced will go out of scope + state.always_search = false; + + result + } + /// Evaluate a statement /// /// @@ -1886,23 +1919,7 @@ impl Engine { // Block scope Stmt::Block(statements, _) => { - let prev_scope_len = scope.len(); - let prev_mods_len = mods.len(); - state.scope_level += 1; - - let result = statements.iter().try_fold(Default::default(), |_, stmt| { - self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level) - }); - - scope.rewind(prev_scope_len); - mods.truncate(prev_mods_len); - state.scope_level -= 1; - - // The impact of an eval statement goes away at the end of a block - // because any new variables introduced will go out of scope - state.always_search = false; - - result + self.eval_statements(scope, mods, state, lib, this_ptr, statements, level) } // If-else statement diff --git a/src/engine_api.rs b/src/engine_api.rs index 619e5f54..71325e6e 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -1405,7 +1405,7 @@ impl Engine { mods: &mut Imports, ast: &'a AST, ) -> Result<(Dynamic, u64), Box> { - self.eval_statements(scope, mods, ast.statements(), &[ast.lib()]) + self.eval_statements_raw(scope, mods, ast.statements(), &[ast.lib()]) } /// Evaluate a file, but throw away the result and only return error (if any). @@ -1467,7 +1467,7 @@ impl Engine { ast: &AST, ) -> Result<(), Box> { let mut mods = Default::default(); - self.eval_statements(scope, &mut mods, ast.statements(), &[ast.lib()]) + self.eval_statements_raw(scope, &mut mods, ast.statements(), &[ast.lib()]) .map(|_| ()) } diff --git a/src/fn_call.rs b/src/fn_call.rs index 1a645870..2979c401 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -604,9 +604,10 @@ impl Engine { } } - /// Evaluate a list of statements. + /// Evaluate a list of statements with an empty state and no `this` pointer. + /// This is commonly used to evaluate a list of statements in an `AST` or a script function body. #[inline] - pub(crate) fn eval_statements<'a>( + pub(crate) fn eval_statements_raw<'a>( &self, scope: &mut Scope, mods: &mut Imports, @@ -667,7 +668,7 @@ impl Engine { } // Evaluate the AST - let (result, operations) = self.eval_statements(scope, mods, ast.statements(), lib)?; + let (result, operations) = self.eval_statements_raw(scope, mods, ast.statements(), lib)?; state.operations += operations; self.inc_operations(state)?; diff --git a/src/optimize.rs b/src/optimize.rs index 6d9bc23d..3778a7b1 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -409,9 +409,14 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { ))) } // expr; - Stmt::Expr(Expr::Stmt(x, _)) if matches!(*x, Stmt::Expr(_)) => { + Stmt::Expr(Expr::Stmt(x, pos)) if x.is_empty() => { state.set_dirty(); - optimize_stmt(*x, state, preserve_result) + Stmt::Noop(pos) + } + // expr; + Stmt::Expr(Expr::Stmt(mut x, _)) if x.len() == 1 => { + state.set_dirty(); + optimize_stmt(x.remove(0), state, preserve_result) } // expr; Stmt::Expr(expr) => Stmt::Expr(optimize_expr(expr, state)), @@ -438,8 +443,13 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { match expr { // expr - do not promote because there is a reason it is wrapped in an `Expr::Expr` Expr::Expr(x) => Expr::Expr(Box::new(optimize_expr(*x, state))), + // {} + Expr::Stmt(x, pos) if x.is_empty() => { + state.set_dirty(); + Expr::Unit(pos) + } // { stmt } - Expr::Stmt(x, pos) => match *x { + Expr::Stmt(mut x, pos) if x.len() == 1 => match x.remove(0) { // {} -> () Stmt::Noop(_) => { state.set_dirty(); @@ -451,8 +461,12 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { optimize_expr(expr, state) } // { stmt } - stmt => Expr::Stmt(Box::new(optimize_stmt(stmt, state, true)), pos), - }, + stmt => Expr::Stmt(Box::new(vec![optimize_stmt(stmt, state, true)].into()), pos) + } + // { stmt; ... } + Expr::Stmt(x, pos) => Expr::Stmt(Box::new( + x.into_iter().map(|stmt| optimize_stmt(stmt, state, true)).collect(), + ), pos), // lhs.rhs #[cfg(not(feature = "no_object"))] diff --git a/src/parser.rs b/src/parser.rs index bd6d613c..d808522e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -765,8 +765,10 @@ fn parse_primary( let (token, _) = match token { // { - block statement as expression Token::LeftBrace if settings.allow_stmt_expr => { - return parse_block(input, state, lib, settings.level_up()) - .map(|block| Expr::Stmt(Box::new(block), settings.pos)) + return parse_block(input, state, lib, settings.level_up()).map(|block| match block { + Stmt::Block(statements, pos) => Expr::Stmt(Box::new(statements.into()), pos), + _ => unreachable!(), + }) } Token::EOF => return Err(PERR::UnexpectedEOF.into_err(settings.pos)), _ => input.next().unwrap(), @@ -962,10 +964,11 @@ fn parse_unary( match token { // If statement is allowed to act as expressions - Token::If if settings.allow_if_expr => Ok(Expr::Stmt( - Box::new(parse_if(input, state, lib, settings.level_up())?), - settings.pos, - )), + Token::If if settings.allow_if_expr => { + let mut block: StaticVec<_> = Default::default(); + block.push(parse_if(input, state, lib, settings.level_up())?); + Ok(Expr::Stmt(Box::new(block), settings.pos)) + } // -expr Token::UnaryMinus => { let pos = eat_token(input, Token::UnaryMinus); @@ -1657,12 +1660,13 @@ fn parse_custom_syntax( exprs.push(parse_expr(input, state, lib, settings)?); segments.push(MARKER_EXPR.into()); } - MARKER_BLOCK => { - let stmt = parse_block(input, state, lib, settings)?; - let pos = stmt.position(); - exprs.push(Expr::Stmt(Box::new(stmt), pos)); - segments.push(MARKER_BLOCK.into()); - } + MARKER_BLOCK => match parse_block(input, state, lib, settings)? { + Stmt::Block(statements, pos) => { + exprs.push(Expr::Stmt(Box::new(statements.into()), pos)); + segments.push(MARKER_BLOCK.into()); + } + _ => unreachable!(), + }, s => match input.next().unwrap() { (Token::LexError(err), pos) => return Err(err.into_err(pos)), (t, _) if t.syntax().as_ref() == s => { @@ -2525,12 +2529,12 @@ fn make_curry_from_externals(fn_expr: Expr, externals: StaticVec, pos: Po #[cfg(not(feature = "no_closure"))] { // Statement block - let mut statements: Vec<_> = Default::default(); + let mut statements: StaticVec<_> = Default::default(); // Insert `Share` statements statements.extend(externals.into_iter().map(|x| Stmt::Share(Box::new(x)))); // Final expression statements.push(Stmt::Expr(expr)); - Expr::Stmt(Box::new(Stmt::Block(statements, pos)), pos) + Expr::Stmt(Box::new(statements), pos) } #[cfg(feature = "no_closure")] From f75942715d91c36817ae772df5f1bebcb9cf871b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 4 Nov 2020 12:34:34 +0800 Subject: [PATCH 03/10] Remove clippy warnings. --- src/packages/logic.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/packages/logic.rs b/src/packages/logic.rs index ec8cf9a7..f312bc93 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -1,6 +1,10 @@ use crate::def_package; use crate::plugin::*; +#[cfg(any( + not(feature = "no_float"), + all(not(feature = "only_i32"), not(feature = "only_i64")) +))] macro_rules! gen_cmp_functions { ($root:ident => $($arg_type:ident),+) => { mod $root { $(pub mod $arg_type { @@ -37,6 +41,10 @@ macro_rules! gen_cmp_functions { }; } +#[cfg(any( + not(feature = "no_float"), + all(not(feature = "only_i32"), not(feature = "only_i64")) +))] macro_rules! reg_functions { ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $( combine_with_exported_module!($mod_name, "logic", $root::$arg_type::functions); @@ -54,7 +62,13 @@ def_package!(crate:LogicPackage:"Logical operators.", lib, { } #[cfg(not(feature = "no_float"))] - reg_functions!(lib += float; f32); + { + #[cfg(not(feature = "f32_float"))] + reg_functions!(lib += float; f32); + + #[cfg(feature = "f32_float")] + reg_functions!(lib += float; f64); + } set_exported_fn!(lib, "!", not); }); @@ -75,4 +89,9 @@ gen_cmp_functions!(numbers => i8, u8, i16, u16, i32, u32, u64); gen_cmp_functions!(num_128 => i128, u128); #[cfg(not(feature = "no_float"))] +#[cfg(not(feature = "f32_float"))] gen_cmp_functions!(float => f32); + +#[cfg(not(feature = "no_float"))] +#[cfg(feature = "f32_float")] +gen_cmp_functions!(float => f64); From c287a61b93d0bdaef6e317699bd9d11f5bbfc377 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 4 Nov 2020 12:34:54 +0800 Subject: [PATCH 04/10] Streamline ScriptFnDef. --- src/ast.rs | 26 ++++++++++++++++++++------ src/optimize.rs | 1 - src/parser.rs | 2 -- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index ebf38250..4a31d1f9 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -83,6 +83,10 @@ impl FnAccess { /// This type is volatile and may change. #[derive(Debug, Clone)] pub struct ScriptFnDef { + /// Function body. + pub body: Stmt, + /// Encapsulated running environment, if any. + pub lib: Option>, /// Function name. pub name: ImmutableString, /// Function access mode. @@ -92,12 +96,6 @@ pub struct ScriptFnDef { /// Access to external variables. #[cfg(not(feature = "no_closure"))] pub externals: HashSet, - /// Function body. - pub body: Stmt, - /// Position of the function definition. - pub pos: Position, - /// Encapsulated running environment, if any. - pub lib: Option>, } impl fmt::Display for ScriptFnDef { @@ -1240,3 +1238,19 @@ impl Expr { } } } + +#[cfg(test)] +mod tests { + use super::*; + + /// This test is to make sure no code changes increase the sizes of critical data structures. + #[test] + fn check_struct_sizes() { + use std::mem::size_of; + + assert_eq!(size_of::(), 16); + assert_eq!(size_of::>(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 32); + } +} diff --git a/src/optimize.rs b/src/optimize.rs index 3778a7b1..e549fc8b 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -866,7 +866,6 @@ pub fn optimize_into_ast( params: fn_def.params.clone(), #[cfg(not(feature = "no_closure"))] externals: fn_def.externals.clone(), - pos: fn_def.pos, lib: None, } .into() diff --git a/src/parser.rs b/src/parser.rs index d808522e..448df46f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2485,7 +2485,6 @@ fn parse_fn( #[cfg(not(feature = "no_closure"))] externals, body, - pos: settings.pos, lib: None, }) } @@ -2655,7 +2654,6 @@ fn parse_anon_fn( #[cfg(not(feature = "no_closure"))] externals: Default::default(), body, - pos: settings.pos, lib: None, }; From b390586bcc83171673f6c702259b22f52f10a768 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 4 Nov 2020 13:11:37 +0800 Subject: [PATCH 05/10] Fix optimization of Stmt::Expr. --- src/optimize.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/optimize.rs b/src/optimize.rs index e549fc8b..862aa855 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -408,15 +408,15 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { pos, ))) } - // expr; + // {} Stmt::Expr(Expr::Stmt(x, pos)) if x.is_empty() => { state.set_dirty(); Stmt::Noop(pos) } - // expr; - Stmt::Expr(Expr::Stmt(mut x, _)) if x.len() == 1 => { + // {...}; + Stmt::Expr(Expr::Stmt(x, pos)) => { state.set_dirty(); - optimize_stmt(x.remove(0), state, preserve_result) + Stmt::Block(x.into_vec(), pos) } // expr; Stmt::Expr(expr) => Stmt::Expr(optimize_expr(expr, state)), @@ -449,7 +449,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { Expr::Unit(pos) } // { stmt } - Expr::Stmt(mut x, pos) if x.len() == 1 => match x.remove(0) { + Expr::Stmt(mut x, pos) if x.len() == 1 => match x.pop().unwrap() { // {} -> () Stmt::Noop(_) => { state.set_dirty(); From a5d22ddd45511a479581308a7c8570a4e6e2ced4 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 4 Nov 2020 14:37:46 +0800 Subject: [PATCH 06/10] Make FuncInfo a struct. --- src/engine_api.rs | 4 +- src/module/mod.rs | 159 +++++++++++++++++++++++++++++----------------- 2 files changed, 101 insertions(+), 62 deletions(-) diff --git a/src/engine_api.rs b/src/engine_api.rs index 71325e6e..135fec21 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -1661,8 +1661,8 @@ impl Engine { let lib = if cfg!(not(feature = "no_function")) { ast.lib() .iter_fn() - .filter(|(_, _, _, _, f)| f.is_script()) - .map(|(_, _, _, _, f)| f.get_fn_def().clone()) + .filter(|f| f.func.is_script()) + .map(|f| f.func.get_fn_def().clone()) .collect() } else { Default::default() diff --git a/src/module/mod.rs b/src/module/mod.rs index 8c9a17a3..c4853cf3 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -37,13 +37,20 @@ use crate::stdlib::{ vec::Vec, }; -pub type FuncInfo = ( - String, - FnAccess, - usize, - Option>, - CallableFunction, -); +/// Data structure containing a single registered function. +#[derive(Debug, Clone)] +pub struct FuncInfo { + /// Function instance. + pub func: CallableFunction, + /// Function access mode. + pub access: FnAccess, + /// Function name. + pub name: String, + /// Number of parameters. + pub params: usize, + /// Parameter types (if applicable). + pub types: Option>, +} /// An imported module, which may contain variables, sub-modules, /// external Rust functions, and script-defined functions. @@ -91,7 +98,7 @@ impl fmt::Debug for Module { .join(", "), self.functions .values() - .map(|(_, _, _, _, f)| f.to_string()) + .map(|FuncInfo { func, .. }| func.to_string()) .collect::>() .join(", "), ) @@ -290,13 +297,13 @@ impl Module { let hash_script = calc_script_fn_hash(empty(), &fn_def.name, num_params); self.functions.insert( hash_script, - ( - fn_def.name.to_string(), - fn_def.access, - num_params, - None, - fn_def.into(), - ), + FuncInfo { + name: fn_def.name.to_string(), + access: fn_def.access, + params: num_params, + types: None, + func: fn_def.into(), + }, ); self.indexed = false; hash_script @@ -313,12 +320,19 @@ impl Module { ) -> Option<&Shared> { self.functions .values() - .find(|(fn_name, access, num, _, _)| { - (!public_only || *access == FnAccess::Public) - && *num == num_params - && fn_name == name - }) - .map(|(_, _, _, _, f)| f.get_shared_fn_def()) + .find( + |FuncInfo { + name: fn_name, + access, + params, + .. + }| { + (!public_only || *access == FnAccess::Public) + && *params == num_params + && fn_name == name + }, + ) + .map(|FuncInfo { func, .. }| func.get_shared_fn_def()) } /// Does a sub-module exist in the module? @@ -414,7 +428,7 @@ impl Module { } else if public_only { self.functions .get(&hash_fn) - .map(|(_, access, _, _, _)| access.is_public()) + .map(|FuncInfo { access, .. }| access.is_public()) .unwrap_or(false) } else { self.functions.contains_key(&hash_fn) @@ -453,7 +467,13 @@ impl Module { self.functions.insert( hash_fn, - (name, access, params.len(), Some(params), func.into()), + FuncInfo { + name, + access, + params: params.len(), + types: Some(params), + func: func.into(), + }, ); self.indexed = false; @@ -1091,9 +1111,9 @@ impl Module { } else { self.functions .get(&hash_fn) - .and_then(|(_, access, _, _, f)| match access { - _ if !public_only => Some(f), - FnAccess::Public => Some(f), + .and_then(|FuncInfo { access, func, .. }| match access { + _ if !public_only => Some(func), + FnAccess::Public => Some(func), FnAccess::Private => None, }) } @@ -1194,7 +1214,7 @@ impl Module { other .functions .iter() - .filter(|(_, (_, _, _, _, v))| match v { + .filter(|(_, FuncInfo { func, .. })| match func { #[cfg(not(feature = "no_function"))] CallableFunction::Script(f) => { _filter(f.access, f.name.as_str(), f.params.len()) @@ -1218,10 +1238,11 @@ impl Module { &mut self, mut filter: impl FnMut(FnAccess, &str, usize) -> bool, ) -> &mut Self { - self.functions.retain(|_, (_, _, _, _, v)| match v { - CallableFunction::Script(f) => filter(f.access, f.name.as_str(), f.params.len()), - _ => true, - }); + self.functions + .retain(|_, FuncInfo { func, .. }| match func { + CallableFunction::Script(f) => filter(f.access, f.name.as_str(), f.params.len()), + _ => true, + }); self.all_functions.clear(); self.all_variables.clear(); @@ -1266,7 +1287,7 @@ impl Module { ) -> impl Iterator)> + 'a { self.functions .values() - .map(|(_, _, _, _, f)| f) + .map(|f| &f.func) .filter(|f| f.is_script()) .map(CallableFunction::get_shared_fn_def) .map(|f| { @@ -1285,10 +1306,14 @@ impl Module { #[cfg(not(feature = "internals"))] #[inline(always)] pub fn iter_script_fn_info(&self) -> impl Iterator { - self.functions - .values() - .filter(|(_, _, _, _, f)| f.is_script()) - .map(|(name, access, num_params, _, _)| (*access, name.as_str(), *num_params)) + self.functions.values().filter(|f| f.func.is_script()).map( + |FuncInfo { + name, + access, + params, + .. + }| (*access, name.as_str(), *params), + ) } /// Get an iterator over all script-defined functions in the module. @@ -1401,31 +1426,45 @@ impl Module { module .functions .iter() - .filter(|(_, (_, access, _, _, _))| access.is_public()) - .for_each(|(&_hash, (name, _, _num_params, params, func))| { - if let Some(params) = params { - // Qualified 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_qualified_script = - calc_script_fn_hash(qualifiers.iter().cloned(), name, params.len()); - // 2) Calculate a second hash with no qualifiers, empty function name, - // and the actual list of argument `TypeId`'.s - let hash_fn_args = calc_native_fn_hash(empty(), "", params.iter().cloned()); - // 3) The final hash is the XOR of the two hashes. - let hash_qualified_fn = hash_qualified_script ^ hash_fn_args; + .filter(|(_, FuncInfo { access, .. })| access.is_public()) + .for_each( + |( + &_hash, + FuncInfo { + name, + params, + types, + func, + .. + }, + )| { + if let Some(param_types) = types { + assert_eq!(*params, param_types.len()); - functions.push((hash_qualified_fn, func.clone())); - } else if cfg!(not(feature = "no_function")) { - let hash_qualified_script = if qualifiers.is_empty() { - _hash - } else { - // Qualifiers + function name + number of arguments. - calc_script_fn_hash(qualifiers.iter().map(|&v| v), &name, *_num_params) - }; - functions.push((hash_qualified_script, func.clone())); - } - }); + // Qualified 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_qualified_script = + calc_script_fn_hash(qualifiers.iter().cloned(), name, *params); + // 2) Calculate a second hash with no qualifiers, empty function name, + // and the actual list of argument `TypeId`'.s + let hash_fn_args = + calc_native_fn_hash(empty(), "", param_types.iter().cloned()); + // 3) The final hash is the XOR of the two hashes. + let hash_qualified_fn = hash_qualified_script ^ hash_fn_args; + + functions.push((hash_qualified_fn, func.clone())); + } else if cfg!(not(feature = "no_function")) { + let hash_qualified_script = if qualifiers.is_empty() { + _hash + } else { + // Qualifiers + function name + number of arguments. + calc_script_fn_hash(qualifiers.iter().map(|&v| v), &name, *params) + }; + functions.push((hash_qualified_script, func.clone())); + } + }, + ); } if !self.indexed { From 7e60e9ff5475605824bb21259ba2d6dc8cc3932b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 4 Nov 2020 17:10:26 +0800 Subject: [PATCH 07/10] Docs revision. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 62b04dbf..8893d420 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Standard features * Fairly low compile-time overhead. * Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM). * Tight integration with native Rust [functions](https://schungx.github.io/rhai/rust/functions.html) and [types]([#custom-types-and-methods](https://schungx.github.io/rhai/rust/custom.html)), including [getters/setters](https://schungx.github.io/rhai/rust/getters-setters.html), [methods](https://schungx.github.io/rhai/rust/custom.html) and [indexers](https://schungx.github.io/rhai/rust/indexers.html). -* Freely pass Rust variables/constants into a script via an external [`Scope`](https://schungx.github.io/rhai/rust/scope.html). +* Freely pass Rust variables/constants into a script via an external [`Scope`](https://schungx.github.io/rhai/rust/scope.html) - all clonable Rust types are supported; no need to implement any special trait. * Easily [call a script-defined function](https://schungx.github.io/rhai/engine/call-fn.html) from Rust. * Relatively little `unsafe` code (yes there are some for performance reasons). * Few dependencies (currently only [`smallvec`](https://crates.io/crates/smallvec)). From da1c5e364d1c5e27b12ab1fc41f346ea45f05aeb Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 4 Nov 2020 17:11:56 +0800 Subject: [PATCH 08/10] Docs revision. --- doc/src/about/features.md | 12 ++++++++---- doc/src/rust/custom.md | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/doc/src/about/features.md b/doc/src/about/features.md index 101c631d..1f5ca2ef 100644 --- a/doc/src/about/features.md +++ b/doc/src/about/features.md @@ -8,9 +8,11 @@ Easy * Easy-to-use language similar to JavaScript+Rust with dynamic typing. -* Tight integration with native Rust [functions] and [types][custom types], including [getters/setters], [methods][custom type] and [indexers]. +* Tight integration with native Rust [functions] and [types][custom types] including [getters/setters], + [methods][custom type] and [indexers]. -* Freely pass Rust variables/constants into a script via an external [`Scope`]. +* Freely pass Rust variables/constants into a script via an external [`Scope`] - all clonable Rust types are supported seamlessly + without the need to implement any special trait. * Easily [call a script-defined function]({{rootUrl}}/engine/call-fn.md) from Rust. @@ -51,12 +53,14 @@ Safe * Relatively little `unsafe` code (yes there are some for performance reasons). -* Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment unless [explicitly permitted]({{rootUrl}}/patterns/control.md). +* Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment unless + [explicitly permitted]({{rootUrl}}/patterns/control.md). Rugged ------ -* Protected against malicious attacks (such as [stack-overflow][maximum call stack depth], [over-sized data][maximum length of strings], and [runaway scripts][maximum number of operations] etc.) that may come from untrusted third-party user-land scripts. +* Protected against malicious attacks (such as [stack-overflow][maximum call stack depth], [over-sized data][maximum length of strings], + and [runaway scripts][maximum number of operations] etc.) that may come from untrusted third-party user-land scripts. * Track script evaluation [progress] and manually terminate a script run. diff --git a/doc/src/rust/custom.md b/doc/src/rust/custom.md index 59815079..4c187d78 100644 --- a/doc/src/rust/custom.md +++ b/doc/src/rust/custom.md @@ -11,7 +11,7 @@ Rhai works seamlessly with _any_ Rust type. The type can be _anything_; it does have any prerequisites other than being `Clone`. It does not need to implement any other trait or use any custom `#[derive]`. -This allows Rhai to be integrated into an existing code base with as little plumbing +This allows Rhai to be integrated into an existing Rust code base with as little plumbing as possible, usually silently and seamlessly. External types that are not defined within the same crate (and thus cannot implement special Rhai traits or use special `#[derive]`) can also be used easily with Rhai. From dd53937ddd1bcf703edbba8f394a5acb4e98015e Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 4 Nov 2020 17:12:21 +0800 Subject: [PATCH 09/10] f32_float for no-std example. --- no_std/no_std_test/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/no_std/no_std_test/Cargo.toml b/no_std/no_std_test/Cargo.toml index 723cbdfc..cd151bad 100644 --- a/no_std/no_std_test/Cargo.toml +++ b/no_std/no_std_test/Cargo.toml @@ -12,7 +12,7 @@ homepage = "https://github.com/jonathandturner/rhai/tree/no_std/no_std_test" repository = "https://github.com/jonathandturner/rhai" [dependencies] -rhai = { path = "../../", features = [ "no_std", "unchecked", "only_i32", "no_module" ], default_features = false } +rhai = { path = "../../", features = [ "no_std", "unchecked", "only_i32", "f32_float", "no_module" ], default_features = false } wee_alloc = { version = "0.4.5", default_features = false } [profile.dev] From 99669b5909bf3fec5b8ff674a5e25915ce44c657 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 4 Nov 2020 17:17:21 +0800 Subject: [PATCH 10/10] Bump version. --- Cargo.toml | 2 +- doc/src/context.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6311ac79..b4b99a8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ members = [ [package] name = "rhai" -version = "0.19.3" +version = "0.19.4" edition = "2018" authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"] description = "Embedded scripting for Rust" diff --git a/doc/src/context.json b/doc/src/context.json index 7fb6c03e..7bf82955 100644 --- a/doc/src/context.json +++ b/doc/src/context.json @@ -1,5 +1,5 @@ { - "version": "0.19.3", + "version": "0.19.4", "repoHome": "https://github.com/jonathandturner/rhai/blob/master", "repoTree": "https://github.com/jonathandturner/rhai/tree/master", "rootUrl": "",