From 1e8978f921caa85743c0253224c5f17239e0e03f Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 26 Jul 2020 16:35:13 +0800 Subject: [PATCH 01/64] Revert "Delete benchmark.yml" This reverts commit 5dc244ddc8cd37e0ebbef0d54f8161d729cc3973. --- .github/workflows/benchmark.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/benchmark.yml diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 00000000..df310705 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,29 @@ +name: Benchmark +on: + push: + branches: + - master + +jobs: + benchmark: + name: Run Rust benchmark + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: rustup toolchain update nightly && rustup default nightly + - name: Run benchmark + run: cargo +nightly bench | tee output.txt + - name: Store benchmark result + uses: rhysd/github-action-benchmark@v1 + with: + name: Rust Benchmark + tool: 'cargo' + output-file-path: output.txt + # Use personal access token instead of GITHUB_TOKEN due to https://github.community/t5/GitHub-Actions/Github-action-not-triggering-gh-pages-upon-push/td-p/26869/highlight/false + github-token: ${{ secrets.RHAI }} + auto-push: true + # Show alert with commit comment on detecting possible performance regression + alert-threshold: '200%' + comment-on-alert: true + fail-on-alert: true + alert-comment-cc-users: '@schungx' From 878ba0b7941ea97c1172014d6bba5ea430c0ca5c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 26 Jul 2020 18:17:55 +0800 Subject: [PATCH 02/64] Remove register_raw_fn_XXX API's. --- RELEASES.md | 18 ++-- doc/src/engine/custom-syntax.md | 5 +- doc/src/rust/register-raw.md | 38 ++++----- src/api.rs | 141 -------------------------------- tests/call_fn.rs | 10 +-- 5 files changed, 29 insertions(+), 183 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 22aae65d..520a2bf1 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -6,24 +6,32 @@ Version 0.18.0 This version adds: -* Anonymous functions (in closure syntax). Simplifies creation of ad hoc functions. +* Binding the `this` pointer in a function pointer `call`. +* Anonymous functions (in Rust closure syntax). Simplifies creation of single-use ad-hoc functions. * Currying of function pointers. New features ------------ * `call` can now be called function-call style for function pointers - this is to handle builds with `no_object`. -* Disallow many keywords as variables, such as `print`, `eval`, `call`, `this` etc. +* Reserve language keywords, such as `print`, `eval`, `call`, `this` etc. * `x.call(f, ...)` allows binding `x` to `this` for the function referenced by the function pointer `f`. -* Anonymous functions in the syntax of a closure, e.g. `|x, y, z| x + y - z`. +* Anonymous functions are supported in the syntax of a Rust closure, e.g. `|x, y, z| x + y - z`. * Custom syntax now works even without the `internals` feature. -* Currying of function pointers is supported via the `curry` keyword. +* Currying of function pointers is supported via the new `curry` keyword. * `Module::set_indexer_get_set_fn` is added as a shorthand of both `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn`. Breaking changes ---------------- +* Language keywords are now _reserved_ (even when disabled) and they can no longer be used as variable names. * Function signature for defining custom syntax is simplified. +* `Engine::register_raw_fn_XXX` API shortcuts are removed. + +Housekeeping +------------ + +* Most compilation warnings are eliminated via feature gates. Version 0.17.0 @@ -57,7 +65,7 @@ New features * `Engine::disable_symbol` to surgically disable keywords and/or operators. * `Engine::register_custom_operator` to define a custom operator. * `Engine::register_custom_syntax` to define a custom syntax. -* New low-level API `Engine::register_raw_fn` and `Engine::register_raw_fn_XXX`. +* New low-level API `Engine::register_raw_fn`. * New low-level API `Module::set_raw_fn` mirroring `Engine::register_raw_fn`. * `AST::clone_functions_only`, `AST::clone_functions_only_filtered` and `AST::clone_statements_only` to clone only part of an `AST`. * The boolean `^` (XOR) operator is added. diff --git a/doc/src/engine/custom-syntax.md b/doc/src/engine/custom-syntax.md index 89fdf792..8f308106 100644 --- a/doc/src/engine/custom-syntax.md +++ b/doc/src/engine/custom-syntax.md @@ -114,10 +114,7 @@ Any custom syntax must include an _implementation_ of it. The function signature of an implementation is: -```rust -Fn(engine: &Engine, context: &mut EvalContext, scope: &mut Scope, inputs: &[Expression]) - -> Result> -``` +> `Fn(engine: &Engine, context: &mut EvalContext, scope: &mut Scope, inputs: &[Expression]) -> Result>` where: diff --git a/doc/src/rust/register-raw.md b/doc/src/rust/register-raw.md index 6d3f22c5..76866249 100644 --- a/doc/src/rust/register-raw.md +++ b/doc/src/rust/register-raw.md @@ -55,27 +55,12 @@ engine.register_fn("increment_by", |x: &mut i64, y: i64| x += y); ``` -Shortcuts ---------- +Function Signature +------------------ -As usual with Rhai, there are shortcuts. For functions of zero to four parameters, which should be -the majority, use one of the `Engine::register_raw_fn_n` (where `n = 0..4`) methods: +The function signature passed to `Engine::register_raw_fn` takes the following form: -```rust -// Specify parameter types as generics -engine.register_raw_fn_2::( - "increment_by", - |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { ... } -); -``` - - -Closure Signature ------------------ - -The closure passed to `Engine::register_raw_fn` takes the following form: - -`Fn(engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]) -> Result> + 'static` +> `Fn(engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]) -> Result> + 'static` where: @@ -113,6 +98,12 @@ there can be no other immutable references to `args`, otherwise the Rust borrow Example - Passing a Function Pointer to a Rust Function ------------------------------------------------------ +The low-level API is useful when there is a need to interact with the scripting [`Engine`] within a function. + +The following example registers a function that takes a [function pointer] as an argument, +then calls it within the same [`Engine`]. This way, a _callback_ function can be provided +to a native Rust function. + ```rust use rhai::{Engine, Module, Dynamic, FnPtr}; @@ -133,11 +124,10 @@ engine.register_raw_fn( let value = args[2].clone(); // 3rd argument - function argument let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer - // Use 'call_fn_dynamic' to call the function name. - // Pass 'lib' as the current global library of functions. - engine.call_fn_dynamic(&mut Scope::new(), lib, fp.fn_name(), Some(this_ptr), [value])?; - - Ok(()) + // Use 'FnPtr::call_dynamic' to call the function pointer. + // Beware, only script-defined functions are supported by 'FnPtr::call_dynamic'. + // If it is a native Rust function, directly call it here in Rust instead! + fp.call_dynamic(engine, lib, Some(this_ptr), [value]) }, ); diff --git a/src/api.rs b/src/api.rs index b7a1bb74..573a3366 100644 --- a/src/api.rs +++ b/src/api.rs @@ -62,147 +62,6 @@ impl Engine { self } - /// Register a function of no parameters with the `Engine`. - /// - /// ## WARNING - Low Level API - /// - /// This function is very low level. - #[deprecated(note = "this function is volatile and may change")] - pub fn register_raw_fn_0( - &mut self, - name: &str, - func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn + SendSync + 'static, - ) -> &mut Self { - self.global_module.set_raw_fn(name, &[], func); - self - } - - /// Register a function of one parameter with the `Engine`. - /// - /// ## WARNING - Low Level API - /// - /// This function is very low level. - /// - /// Arguments are simply passed in as a mutable array of `&mut Dynamic`, - /// which is guaranteed to contain enough arguments of the correct types. - /// - /// To access a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::()` - /// - /// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::()`. - /// Notice that this will _consume_ the argument, replacing it with `()`. - /// - /// To access the first mutable parameter, use `args.get_mut(0).unwrap()` - #[deprecated(note = "this function is volatile and may change")] - pub fn register_raw_fn_1( - &mut self, - name: &str, - func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn + SendSync + 'static, - ) -> &mut Self { - self.global_module - .set_raw_fn(name, &[TypeId::of::()], func); - self - } - - /// Register a function of two parameters with the `Engine`. - /// - /// ## WARNING - Low Level API - /// - /// This function is very low level. - /// - /// Arguments are simply passed in as a mutable array of `&mut Dynamic`, - /// which is guaranteed to contain enough arguments of the correct types. - /// - /// To access a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::()` - /// - /// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::()`. - /// Notice that this will _consume_ the argument, replacing it with `()`. - /// - /// To access the first mutable parameter, use `args.get_mut(0).unwrap()` - #[deprecated(note = "this function is volatile and may change")] - pub fn register_raw_fn_2( - &mut self, - name: &str, - func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn + SendSync + 'static, - ) -> &mut Self { - self.global_module - .set_raw_fn(name, &[TypeId::of::(), TypeId::of::()], func); - self - } - - /// Register a function of three parameters with the `Engine`. - /// - /// ## WARNING - Low Level API - /// - /// This function is very low level. - /// - /// Arguments are simply passed in as a mutable array of `&mut Dynamic`, - /// which is guaranteed to contain enough arguments of the correct types. - /// - /// To access a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::()` - /// - /// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::()`. - /// Notice that this will _consume_ the argument, replacing it with `()`. - /// - /// To access the first mutable parameter, use `args.get_mut(0).unwrap()` - #[deprecated(note = "this function is volatile and may change")] - pub fn register_raw_fn_3< - A: Variant + Clone, - B: Variant + Clone, - C: Variant + Clone, - T: Variant + Clone, - >( - &mut self, - name: &str, - func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn + SendSync + 'static, - ) -> &mut Self { - self.global_module.set_raw_fn( - name, - &[TypeId::of::(), TypeId::of::(), TypeId::of::()], - func, - ); - self - } - - /// Register a function of four parameters with the `Engine`. - /// - /// ## WARNING - Low Level API - /// - /// This function is very low level. - /// - /// Arguments are simply passed in as a mutable array of `&mut Dynamic`, - /// which is guaranteed to contain enough arguments of the correct types. - /// - /// To access a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::()` - /// - /// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::()`. - /// Notice that this will _consume_ the argument, replacing it with `()`. - /// - /// To access the first mutable parameter, use `args.get_mut(0).unwrap()` - #[deprecated(note = "this function is volatile and may change")] - pub fn register_raw_fn_4< - A: Variant + Clone, - B: Variant + Clone, - C: Variant + Clone, - D: Variant + Clone, - T: Variant + Clone, - >( - &mut self, - name: &str, - func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn + SendSync + 'static, - ) -> &mut Self { - self.global_module.set_raw_fn( - name, - &[ - TypeId::of::(), - TypeId::of::(), - TypeId::of::(), - TypeId::of::(), - ], - func, - ); - self - } - /// Register a custom type for use with the `Engine`. /// The type must implement `Clone`. /// diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 9808ff70..3b93d520 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -131,15 +131,7 @@ fn test_fn_ptr_raw() -> Result<(), Box> { let value = args[2].clone(); let this_ptr = args.get_mut(0).unwrap(); - engine.call_fn_dynamic( - &mut Scope::new(), - lib, - fp.fn_name(), - Some(this_ptr), - [value], - )?; - - Ok(()) + fp.call_dynamic(engine, lib, Some(this_ptr), [value]) }, ); From ce202487920c3212e52488119c4e48ed8a161de6 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 26 Jul 2020 21:57:30 +0800 Subject: [PATCH 03/64] Add more reserved keywords. --- doc/src/appendix/keywords.md | 91 ++++++++++++++++++++++++------------ doc/src/language/keywords.md | 31 ++++++------ src/token.rs | 6 ++- 3 files changed, 84 insertions(+), 44 deletions(-) diff --git a/doc/src/appendix/keywords.md b/doc/src/appendix/keywords.md index 7c8dbaff..4609f9e4 100644 --- a/doc/src/appendix/keywords.md +++ b/doc/src/appendix/keywords.md @@ -3,32 +3,65 @@ Keywords List {{#include ../links.md}} -| Keyword | Description | Not available under | -| :-------------------: | ---------------------------------------- | :-----------------: | -| `true` | Boolean true literal | | -| `false` | Boolean false literal | | -| `let` | Variable declaration | | -| `const` | Constant declaration | | -| `if` | If statement | | -| `else` | else block of if statement | | -| `while` | While loop | | -| `loop` | Infinite loop | | -| `for` | For loop | | -| `in` | Containment test, part of for loop | | -| `continue` | Continue a loop at the next iteration | | -| `break` | Loop breaking | | -| `return` | Return value | | -| `throw` | Throw exception | | -| `import` | Import module | [`no_module`] | -| `export` | Export variable | [`no_module`] | -| `as` | Alias for variable export | [`no_module`] | -| `private` | Mark function private | [`no_function`] | -| `fn` (lower-case `f`) | Function definition | [`no_function`] | -| `Fn` (capital `F`) | Function to create a [function pointer] | | -| `call` | Call a [function pointer] | | -| `curry` | Curry a [function pointer] | | -| `this` | Reference to base object for method call | [`no_function`] | -| `type_of` | Get type name of value | | -| `print` | Print value | | -| `debug` | Print value in debug format | | -| `eval` | Evaluate script | | +| Keyword | Description | Inactive under | +| :-------------------: | ---------------------------------------- | :-------------: | +| `true` | Boolean true literal | | +| `false` | Boolean false literal | | +| `let` | Variable declaration | | +| `const` | Constant declaration | | +| `if` | If statement | | +| `else` | else block of if statement | | +| `while` | While loop | | +| `loop` | Infinite loop | | +| `for` | For loop | | +| `in` | Containment test, part of for loop | | +| `continue` | Continue a loop at the next iteration | | +| `break` | Loop breaking | | +| `return` | Return value | | +| `throw` | Throw exception | | +| `import` | Import module | [`no_module`] | +| `export` | Export variable | [`no_module`] | +| `as` | Alias for variable export | [`no_module`] | +| `private` | Mark function private | [`no_function`] | +| `fn` (lower-case `f`) | Function definition | [`no_function`] | +| `Fn` (capital `F`) | Function to create a [function pointer] | | +| `call` | Call a [function pointer] | | +| `curry` | Curry a [function pointer] | | +| `this` | Reference to base object for method call | [`no_function`] | +| `type_of` | Get type name of value | | +| `print` | Print value | | +| `debug` | Print value in debug format | | +| `eval` | Evaluate script | | + + +Reserved Keywords +----------------- + +| Keyword | Potential usage | +| --------- | --------------------- | +| `var` | Variable declaration | +| `static` | Variable declaration | +| `do` | Looping | +| `each` | Looping | +| `then` | Conditional | +| `goto` | Jump | +| `switch` | Matching | +| `match` | Matching | +| `case` | Matching | +| `public` | Function/field access | +| `new` | Constructor | +| `try` | Trap exception | +| `catch` | Catch exception | +| `use` | Import namespace | +| `with` | Scope | +| `module` | Module | +| `package` | Package | +| `spawn` | Threading | +| `go` | Threading | +| `await` | Async | +| `async` | Async | +| `yield` | Async | +| `default` | Special value | +| `void` | Special value | +| `null` | Special value | +| `nil` | Special value | diff --git a/doc/src/language/keywords.md b/doc/src/language/keywords.md index 836e9b0c..bfaaa356 100644 --- a/doc/src/language/keywords.md +++ b/doc/src/language/keywords.md @@ -5,18 +5,21 @@ Keywords The following are reserved keywords in Rhai: -| Keywords | Usage | Not available under feature | -| ------------------------------------------------- | --------------------- | :-------------------------: | -| `true`, `false` | Boolean constants | | -| `let`, `const` | Variable declarations | | -| `if`, `else` | Control flow | | -| `while`, `loop`, `for`, `in`, `continue`, `break` | Looping | | -| `fn`, `private` | Functions | [`no_function`] | -| `return` | Return values | | -| `throw` | throw exceptions | | -| `import`, `export`, `as` | Modules | [`no_module`] | -| `Fn`, `call` | Function pointers | | -| `type_of`, `print`, `debug`, `eval` | Special functions | | +| Active keywords | Reserved keywords | Usage | Inactive under feature | +| ------------------------------------------------- | ---------------------------------------- | --------------------- | :--------------------: | +| `true`, `false` | | Boolean constants | | +| `let`, `const` | `var`, `static` | Variable declarations | | +| `if`, `else` | `then`, `goto` | Control flow | | +| | `switch`, `match`, `case` | Matching | | +| `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | Looping | | +| `fn`, `private` | `public`, `new` | Functions | [`no_function`] | +| `return` | | Return values | | +| `throw` | `try`, `catch` | Throw exceptions | | +| `import`, `export`, `as` | `use`, `with`, `module`, `package` | Modules/packages | [`no_module`] | +| `Fn`, `call`, `curry` | | Function pointers | | +| | `spawn`, `go`, `async`, `await`, `yield` | Threading/async | | +| `type_of`, `print`, `debug`, `eval` | | Special functions | | +| | `default`, `void`, `null`, `nil` | Special values | | + +Keywords cannot become the name of a [function] or [variable], even when they are disabled. -Keywords cannot be the name of a [function] or [variable], unless the relevant feature is enabled. -For example, `fn` is a valid variable name under [`no_function`]. diff --git a/src/token.rs b/src/token.rs index 775e78db..0219360b 100644 --- a/src/token.rs +++ b/src/token.rs @@ -492,9 +492,13 @@ impl Token { #[cfg(feature = "no_module")] "import" | "export" | "as" => Reserved(syntax.into()), - "===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" => { + "===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "public" + | "new" | "use" | "module" | "package" | "var" | "static" | "with" | "do" | "each" + | "then" | "goto" | "switch" | "match" | "case" | "try" | "catch" | "default" + | "void" | "null" | "nil" | "spawn" | "go" | "async" | "await" | "yield" => { Reserved(syntax.into()) } + KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS => Reserved(syntax.into()), From ec5082c328b677c31f35c0ab9aecccff8e272b4e Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 26 Jul 2020 22:25:30 +0800 Subject: [PATCH 04/64] Better handling of reserved keywords. --- doc/src/appendix/keywords.md | 5 ++-- doc/src/language/keywords.md | 2 +- src/api.rs | 23 +++++++++++++---- src/error.rs | 2 +- src/parser.rs | 28 +++++++++++---------- src/token.rs | 48 +++++++++++++++++++++++++++++++----- 6 files changed, 80 insertions(+), 28 deletions(-) diff --git a/doc/src/appendix/keywords.md b/doc/src/appendix/keywords.md index 4609f9e4..f7c1be73 100644 --- a/doc/src/appendix/keywords.md +++ b/doc/src/appendix/keywords.md @@ -43,8 +43,9 @@ Reserved Keywords | `static` | Variable declaration | | `do` | Looping | | `each` | Looping | -| `then` | Conditional | -| `goto` | Jump | +| `then` | Control flow | +| `goto` | Control flow | +| `exit` | Control flow | | `switch` | Matching | | `match` | Matching | | `case` | Matching | diff --git a/doc/src/language/keywords.md b/doc/src/language/keywords.md index bfaaa356..851b7907 100644 --- a/doc/src/language/keywords.md +++ b/doc/src/language/keywords.md @@ -9,7 +9,7 @@ The following are reserved keywords in Rhai: | ------------------------------------------------- | ---------------------------------------- | --------------------- | :--------------------: | | `true`, `false` | | Boolean constants | | | `let`, `const` | `var`, `static` | Variable declarations | | -| `if`, `else` | `then`, `goto` | Control flow | | +| `if`, `else` | `then`, `goto`, `exit` | Control flow | | | | `switch`, `match`, `case` | Matching | | | `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | Looping | | | `fn`, `private` | `public`, `new` | Functions | [`no_function`] | diff --git a/src/api.rs b/src/api.rs index 573a3366..42ff7037 100644 --- a/src/api.rs +++ b/src/api.rs @@ -19,6 +19,7 @@ use crate::engine::{FN_IDX_GET, FN_IDX_SET}; use crate::{ engine::{make_getter, make_setter, Map}, fn_register::RegisterFn, + token::Token, }; #[cfg(not(feature = "no_function"))] @@ -590,7 +591,7 @@ impl Engine { scripts: &[&str], optimization_level: OptimizationLevel, ) -> Result { - let stream = lex(scripts, self); + let stream = lex(scripts, None, self); self.parse(&mut stream.peekable(), scope, optimization_level) } @@ -715,7 +716,19 @@ impl Engine { // Trims the JSON string and add a '#' in front let scripts = ["#", json.trim()]; - let stream = lex(&scripts, self); + let stream = lex( + &scripts, + if has_null { + Some(Box::new(|token| match token { + // If `null` is present, make sure `null` is treated as a variable + Token::Reserved(s) if s == "null" => Token::Identifier(s), + _ => token, + })) + } else { + None + }, + self, + ); let ast = self.parse_global_expr(&mut stream.peekable(), &scope, OptimizationLevel::None)?; @@ -796,7 +809,7 @@ impl Engine { script: &str, ) -> Result { let scripts = [script]; - let stream = lex(&scripts, self); + let stream = lex(&scripts, None, self); { let mut peekable = stream.peekable(); self.parse_global_expr(&mut peekable, scope, self.optimization_level) @@ -951,7 +964,7 @@ impl Engine { script: &str, ) -> Result> { let scripts = [script]; - let stream = lex(&scripts, self); + let stream = lex(&scripts, None, self); // No need to optimize a lone expression let ast = self.parse_global_expr(&mut stream.peekable(), scope, OptimizationLevel::None)?; @@ -1084,7 +1097,7 @@ impl Engine { script: &str, ) -> Result<(), Box> { let scripts = [script]; - let stream = lex(&scripts, self); + let stream = lex(&scripts, None, self); let ast = self.parse(&mut stream.peekable(), scope, self.optimization_level)?; self.consume_ast_with_scope(scope, &ast) } diff --git a/src/error.rs b/src/error.rs index b6901c68..fc92e5ea 100644 --- a/src/error.rs +++ b/src/error.rs @@ -172,7 +172,7 @@ impl ParseErrorType { Self::VariableExpected => "Expecting name of a variable", Self::Reserved(_) => "Invalid use of reserved keyword", Self::ExprExpected(_) => "Expecting an expression", - Self::FnMissingName => "Expecting name in function declaration", + Self::FnMissingName => "Expecting function name in function declaration", Self::FnMissingParams(_) => "Expecting parameters in function declaration", Self::FnDuplicatedParam(_,_) => "Duplicated parameters in function declaration", Self::FnMissingBody(_) => "Expecting body statement block for function declaration", diff --git a/src/parser.rs b/src/parser.rs index baf1be85..51685a6c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -9,7 +9,7 @@ use crate::module::{Module, ModuleRef}; use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::syntax::FnCustomSyntaxEval; -use crate::token::{is_valid_identifier, Position, Token, TokenStream}; +use crate::token::{is_keyword_function, is_valid_identifier, Position, Token, TokenStream}; use crate::utils::{StaticVec, StraightHasherBuilder}; #[cfg(not(feature = "no_function"))] @@ -1044,7 +1044,7 @@ fn parse_paren_expr( } /// Parse a function call. -fn parse_call_expr( +fn parse_fn_call( input: &mut TokenStream, state: &mut ParseState, lib: &mut FunctionsLib, @@ -1553,8 +1553,12 @@ fn parse_primary( Expr::Variable(Box::new(((s, settings.pos), None, 0, index))) } // Function call is allowed to have reserved keyword - Token::Reserved(s) if s != KEYWORD_THIS && input.peek().unwrap().0 == Token::LeftParen => { - Expr::Variable(Box::new(((s, settings.pos), None, 0, None))) + Token::Reserved(s) if input.peek().unwrap().0 == Token::LeftParen => { + if is_keyword_function(&s) { + Expr::Variable(Box::new(((s, settings.pos), None, 0, None))) + } else { + return Err(PERR::Reserved(s).into_err(settings.pos)); + } } // Access to `this` as a variable is OK Token::Reserved(s) if s == KEYWORD_THIS && input.peek().unwrap().0 != Token::LeftParen => { @@ -1601,7 +1605,7 @@ fn parse_primary( (Expr::Variable(x), Token::LeftParen) => { let ((name, pos), modules, _, _) = *x; settings.pos = pos; - parse_call_expr(input, state, lib, name, modules, settings.level_up())? + parse_fn_call(input, state, lib, name, modules, settings.level_up())? } (Expr::Property(_), _) => unreachable!(), // module access @@ -2882,14 +2886,12 @@ fn parse_fn( #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let name = match input.next().unwrap() { - (Token::Identifier(s), _) | (Token::Custom(s), _) | (Token::Reserved(s), _) - if s != KEYWORD_THIS && is_valid_identifier(s.chars()) => - { - s - } - (_, pos) => return Err(PERR::FnMissingName.into_err(pos)), - }; + let (token, pos) = input.next().unwrap(); + + let name = token.into_function_name().map_err(|t| match t { + Token::Reserved(s) => PERR::Reserved(s).into_err(pos), + _ => PERR::FnMissingName.into_err(pos), + })?; match input.peek().unwrap() { (Token::LeftParen, _) => eat_token(input, Token::LeftParen), diff --git a/src/token.rs b/src/token.rs index 0219360b..0aab8ce5 100644 --- a/src/token.rs +++ b/src/token.rs @@ -494,10 +494,9 @@ impl Token { "===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "public" | "new" | "use" | "module" | "package" | "var" | "static" | "with" | "do" | "each" - | "then" | "goto" | "switch" | "match" | "case" | "try" | "catch" | "default" - | "void" | "null" | "nil" | "spawn" | "go" | "async" | "await" | "yield" => { - Reserved(syntax.into()) - } + | "then" | "goto" | "exit" | "switch" | "match" | "case" | "try" | "catch" + | "default" | "void" | "null" | "nil" | "spawn" | "go" | "async" | "await" + | "yield" => Reserved(syntax.into()), KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS => Reserved(syntax.into()), @@ -670,6 +669,15 @@ impl Token { } } + /// Convert a token into a function name, if possible. + pub fn into_function_name(self) -> Result { + match self { + Self::Reserved(s) if is_keyword_function(&s) => Ok(s), + Self::Custom(s) | Self::Identifier(s) if is_valid_identifier(s.chars()) => Ok(s), + _ => Err(self), + } + } + /// Is this token a custom keyword? pub fn is_custom(&self) -> bool { match self { @@ -718,6 +726,16 @@ pub trait InputStream { fn peek_next(&mut self) -> Option; } +pub fn is_keyword_function(name: &str) -> bool { + name == KEYWORD_PRINT + || name == KEYWORD_DEBUG + || name == KEYWORD_TYPE_OF + || name == KEYWORD_EVAL + || name == KEYWORD_FN_PTR + || name == KEYWORD_FN_PTR_CALL + || name == KEYWORD_FN_PTR_CURRY +} + pub fn is_valid_identifier(name: impl Iterator) -> bool { let mut first_alphabetic = false; @@ -1456,13 +1474,15 @@ pub struct TokenIterator<'a, 'e> { pos: Position, /// Input character stream. stream: MultiInputsStream<'a>, + /// A processor function (if any) that maps a token to another. + map: Option Token>>, } impl<'a> Iterator for TokenIterator<'a, '_> { type Item = (Token, Position); fn next(&mut self) -> Option { - match ( + let token = match ( get_next_token(&mut self.stream, &mut self.state, &mut self.pos), self.engine.disabled_symbols.as_ref(), self.engine.custom_keywords.as_ref(), @@ -1544,12 +1564,27 @@ impl<'a> Iterator for TokenIterator<'a, '_> { Some((Token::Reserved(token.syntax().into()), pos)) } (r, _, _) => r, + }; + + match token { + None => None, + Some((token, pos)) => { + if let Some(ref map) = self.map { + Some((map(token), pos)) + } else { + Some((token, pos)) + } + } } } } /// Tokenize an input text stream. -pub fn lex<'a, 'e>(input: &'a [&'a str], engine: &'e Engine) -> TokenIterator<'a, 'e> { +pub fn lex<'a, 'e>( + input: &'a [&'a str], + map: Option Token>>, + engine: &'e Engine, +) -> TokenIterator<'a, 'e> { TokenIterator { engine, state: TokenizeState { @@ -1567,5 +1602,6 @@ pub fn lex<'a, 'e>(input: &'a [&'a str], engine: &'e Engine) -> TokenIterator<'a streams: input.iter().map(|s| s.chars().peekable()).collect(), index: 0, }, + map, } } From 2dd4d9bcf938af9881c19edbd73c44db54eb7806 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 26 Jul 2020 22:37:33 +0800 Subject: [PATCH 05/64] Avoid warnings with only_i32. --- src/packages/arithmetic.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index 61350996..fd95e675 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -18,6 +18,8 @@ use num_traits::{ CheckedShr, CheckedSub, }; +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] use crate::stdlib::ops::{BitAnd, BitOr, BitXor}; #[cfg(any(feature = "unchecked", not(feature = "no_float")))] @@ -145,12 +147,18 @@ where } } // Bit operators +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] fn binary_and(x: T, y: T) -> FuncReturn<::Output> { Ok(x & y) } +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] fn binary_or(x: T, y: T) -> FuncReturn<::Output> { Ok(x | y) } +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] fn binary_xor(x: T, y: T) -> FuncReturn<::Output> { Ok(x ^ y) } From a3a167424b0e6e386da3d691a0177d021de1fef4 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 27 Jul 2020 12:52:32 +0800 Subject: [PATCH 06/64] Allow Rust functions in FnPtr::call_dynamic. --- doc/src/rust/register-raw.md | 3 +- examples/repl.rs | 8 +- src/engine.rs | 66 ++++++++----- src/fn_call.rs | 174 ++++++++++++++++++++++------------- src/fn_native.rs | 48 ++++++++-- src/module.rs | 12 +-- src/optimize.rs | 1 + src/parser.rs | 14 ++- tests/call_fn.rs | 57 +++++++++--- 9 files changed, 248 insertions(+), 135 deletions(-) diff --git a/doc/src/rust/register-raw.md b/doc/src/rust/register-raw.md index 76866249..cf997286 100644 --- a/doc/src/rust/register-raw.md +++ b/doc/src/rust/register-raw.md @@ -125,8 +125,7 @@ engine.register_raw_fn( let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer // Use 'FnPtr::call_dynamic' to call the function pointer. - // Beware, only script-defined functions are supported by 'FnPtr::call_dynamic'. - // If it is a native Rust function, directly call it here in Rust instead! + // Beware, private script-defined functions will not be found. fp.call_dynamic(engine, lib, Some(this_ptr), [value]) }, ); diff --git a/examples/repl.rs b/examples/repl.rs index cf255986..b3877dd0 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -72,15 +72,17 @@ fn main() { println!("=============="); print_help(); - loop { + 'main_loop: loop { print!("rhai> "); stdout().flush().expect("couldn't flush stdout"); input.clear(); loop { - if let Err(err) = stdin().read_line(&mut input) { - panic!("input error: {}", err); + match stdin().read_line(&mut input) { + Ok(0) => break 'main_loop, + Ok(_) => (), + Err(err) => panic!("input error: {}", err), } let line = input.as_str().trim_end(); diff --git a/src/engine.rs b/src/engine.rs index f4435698..aea01500 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -695,8 +695,8 @@ impl Engine { let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val]; self.exec_fn_call( - state, lib, FN_IDX_SET, true, 0, args, is_ref, true, None, - level, + state, lib, FN_IDX_SET, true, 0, args, is_ref, true, false, + None, level, ) .or_else(|err| match *err { // If there is no index setter, no need to set it back because the indexer is read-only @@ -719,8 +719,8 @@ impl Engine { let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val]; self.exec_fn_call( - state, lib, FN_IDX_SET, true, 0, args, is_ref, true, None, - level, + state, lib, FN_IDX_SET, true, 0, args, is_ref, true, false, + None, level, )?; } // Error @@ -741,7 +741,12 @@ impl Engine { match rhs { // xxx.fn_name(arg_expr_list) Expr::FnCall(x) if x.1.is_none() => { - self.make_method_call(state, lib, target, rhs, idx_val, level) + let ((name, native, pos), _, hash, _, def_val) = x.as_ref(); + self.make_method_call( + state, lib, name, *hash, target, idx_val, *def_val, *native, false, + level, + ) + .map_err(|err| err.new_position(*pos)) } // xxx.module::fn_name(...) - syntax error Expr::FnCall(_) => unreachable!(), @@ -770,7 +775,8 @@ impl Engine { let ((_, _, setter), pos) = x.as_ref(); let mut args = [target.as_mut(), _new_val.as_mut().unwrap()]; self.exec_fn_call( - state, lib, setter, true, 0, &mut args, is_ref, true, None, level, + state, lib, setter, true, 0, &mut args, is_ref, true, false, None, + level, ) .map(|(v, _)| (v, true)) .map_err(|err| err.new_position(*pos)) @@ -780,7 +786,8 @@ impl Engine { let ((_, getter, _), pos) = x.as_ref(); let mut args = [target.as_mut()]; self.exec_fn_call( - state, lib, getter, true, 0, &mut args, is_ref, true, None, level, + state, lib, getter, true, 0, &mut args, is_ref, true, false, None, + level, ) .map(|(v, _)| (v, false)) .map_err(|err| err.new_position(*pos)) @@ -797,9 +804,13 @@ impl Engine { } // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr Expr::FnCall(x) if x.1.is_none() => { - let (val, _) = self.make_method_call( - state, lib, target, sub_lhs, idx_val, level, - )?; + let ((name, native, pos), _, hash, _, def_val) = x.as_ref(); + let (val, _) = self + .make_method_call( + state, lib, name, *hash, target, idx_val, *def_val, + *native, false, level, + ) + .map_err(|err| err.new_position(*pos))?; val.into() } // {xxx:map}.module::fn_name(...) - syntax error @@ -826,8 +837,8 @@ impl Engine { let args = &mut arg_values[..1]; let (mut val, updated) = self .exec_fn_call( - state, lib, getter, true, 0, args, is_ref, true, None, - level, + state, lib, getter, true, 0, args, is_ref, true, false, + None, level, ) .map_err(|err| err.new_position(*pos))?; @@ -847,7 +858,7 @@ impl Engine { arg_values[1] = val; self.exec_fn_call( state, lib, setter, true, 0, arg_values, is_ref, true, - None, level, + false, None, level, ) .or_else( |err| match *err { @@ -864,9 +875,13 @@ impl Engine { } // xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr Expr::FnCall(x) if x.1.is_none() => { - let (mut val, _) = self.make_method_call( - state, lib, target, sub_lhs, idx_val, level, - )?; + let ((name, native, pos), _, hash, _, def_val) = x.as_ref(); + let (mut val, _) = self + .make_method_call( + state, lib, name, *hash, target, idx_val, *def_val, + *native, false, level, + ) + .map_err(|err| err.new_position(*pos))?; let val = &mut val; let target = &mut val.into(); @@ -1132,7 +1147,7 @@ impl Engine { let type_name = val.type_name(); let args = &mut [val, &mut _idx]; self.exec_fn_call( - state, _lib, FN_IDX_GET, true, 0, args, is_ref, true, None, _level, + state, _lib, FN_IDX_GET, true, 0, args, is_ref, true, false, None, _level, ) .map(|(v, _)| v.into()) .map_err(|err| match *err { @@ -1188,7 +1203,7 @@ impl Engine { let (r, _) = self .call_fn_raw( - &mut scope, mods, state, lib, op, hashes, args, false, false, + &mut scope, mods, state, lib, op, hashes, args, false, false, false, def_value, level, ) .map_err(|err| err.new_position(rhs.position()))?; @@ -1303,7 +1318,8 @@ impl Engine { // Run function let (value, _) = self .exec_fn_call( - state, lib, op, true, hash, args, false, false, None, level, + state, lib, op, true, hash, args, false, false, false, None, + level, ) .map_err(|err| err.new_position(*op_pos))?; // Set value to LHS @@ -1331,9 +1347,11 @@ impl Engine { &mut self.eval_expr(scope, mods, state, lib, this_ptr, lhs_expr, level)?, &mut rhs_val, ]; - self.exec_fn_call(state, lib, op, true, hash, args, false, false, None, level) - .map(|(v, _)| v) - .map_err(|err| err.new_position(*op_pos))? + self.exec_fn_call( + state, lib, op, true, hash, args, false, false, false, None, level, + ) + .map(|(v, _)| v) + .map_err(|err| err.new_position(*op_pos))? }); match lhs_expr { @@ -1403,7 +1421,7 @@ impl Engine { let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref(); self.make_function_call( scope, mods, state, lib, this_ptr, name, args_expr, *def_val, *hash, *native, - level, + false, level, ) .map_err(|err| err.new_position(*pos)) } @@ -1413,7 +1431,7 @@ impl Engine { let ((name, _, pos), modules, hash, args_expr, def_val) = x.as_ref(); self.make_qualified_function_call( scope, mods, state, lib, this_ptr, modules, name, args_expr, *def_val, *hash, - level, + true, level, ) .map_err(|err| err.new_position(*pos)) } diff --git a/src/fn_call.rs b/src/fn_call.rs index c55124e2..d581268a 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -8,10 +8,10 @@ use crate::engine::{ KEYWORD_TYPE_OF, }; use crate::error::ParseErrorType; -use crate::fn_native::{FnCallArgs, FnPtr}; +use crate::fn_native::{CallableFunction, FnCallArgs, FnPtr}; use crate::module::{Module, ModuleRef}; use crate::optimize::OptimizationLevel; -use crate::parser::{Expr, ImmutableString, AST, INT}; +use crate::parser::{Expr, FnAccess, ImmutableString, AST, INT}; use crate::result::EvalAltResult; use crate::scope::Scope; use crate::token::Position; @@ -105,6 +105,19 @@ fn restore_first_arg<'a>(old_this_ptr: Option<&'a mut Dynamic>, args: &mut FnCal } } +#[inline] +fn check_public_access(func: &CallableFunction) -> Option<&CallableFunction> { + if func.access() == FnAccess::Private { + None + } else { + Some(func) + } +} +#[inline(always)] +fn no_check_access(func: &CallableFunction) -> Option<&CallableFunction> { + Some(func) +} + impl Engine { /// Universal method for calling functions either registered with the `Engine` or written in Rhai. /// Position in `EvalAltResult` is `None` and must be set afterwards. @@ -125,6 +138,7 @@ impl Engine { args: &mut FnCallArgs, is_ref: bool, _is_method: bool, + pub_only: bool, def_val: Option, _level: usize, ) -> Result<(Dynamic, bool), Box> { @@ -132,6 +146,12 @@ impl Engine { let native_only = hash_script == 0; + let check = if pub_only { + check_public_access + } else { + no_check_access + }; + // Check for stack overflow #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "unchecked"))] @@ -150,14 +170,16 @@ impl Engine { // Then search packages // NOTE: We skip script functions for global_module and packages, and native functions for lib let func = if !native_only { - lib.get_fn(hash_script) //.or_else(|| lib.get_fn(hash_fn)) + lib.get_fn(hash_script).and_then(check) //.or_else(|| lib.get_fn(hash_fn)).and_then(check) } else { None } - //.or_else(|| self.global_module.get_fn(hash_script)) + //.or_else(|| self.global_module.get_fn(hash_script)).and_then(check) .or_else(|| self.global_module.get_fn(hash_fn)) - //.or_else(|| self.packages.get_fn(hash_script)) - .or_else(|| self.packages.get_fn(hash_fn)); + .and_then(check) + //.or_else(|| self.packages.get_fn(hash_script)).and_then(check) + .or_else(|| self.packages.get_fn(hash_fn)) + .and_then(check); if let Some(func) = func { #[cfg(not(feature = "no_function"))] @@ -378,18 +400,28 @@ impl Engine { } // Has a system function an override? - fn has_override(&self, lib: &Module, hash_fn: u64, hash_script: u64) -> bool { + fn has_override(&self, lib: &Module, hash_fn: u64, hash_script: u64, pub_only: bool) -> bool { + let check = if pub_only { + check_public_access + } else { + no_check_access + }; + // NOTE: We skip script functions for global_module and packages, and native functions for lib // First check script-defined functions - lib.contains_fn(hash_script) - //|| lib.contains_fn(hash_fn) - // Then check registered functions - //|| self.global_module.contains_fn(hash_script) - || self.global_module.contains_fn(hash_fn) - // Then check packages - //|| self.packages.contains_fn(hash_script) - || self.packages.contains_fn(hash_fn) + lib.get_fn(hash_script) + .and_then(check) + //.or_else(|| lib.get_fn(hash_fn)).and_then(check) + // Then check registered functions + //.or_else(|| self.global_module.get_fn(hash_script)).and_then(check) + .or_else(|| self.global_module.get_fn(hash_fn)) + .and_then(check) + // Then check packages + //.or_else(|| self.packages.get_fn(hash_script)).and_then(check) + .or_else(|| self.packages.get_fn(hash_fn)) + .and_then(check) + .is_some() } /// Perform an actual function call, taking care of special functions @@ -410,6 +442,7 @@ impl Engine { args: &mut FnCallArgs, is_ref: bool, is_method: bool, + pub_only: bool, def_val: Option, level: usize, ) -> Result<(Dynamic, bool), Box> { @@ -420,7 +453,9 @@ impl Engine { match fn_name { // type_of - KEYWORD_TYPE_OF if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => { + KEYWORD_TYPE_OF + if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1, pub_only) => + { Ok(( self.map_type_name(args[0].type_name()).to_string().into(), false, @@ -428,7 +463,9 @@ impl Engine { } // Fn - KEYWORD_FN_PTR if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => { + KEYWORD_FN_PTR + if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1, pub_only) => + { Err(Box::new(EvalAltResult::ErrorRuntime( "'Fn' should not be called in method style. Try Fn(...);".into(), Position::none(), @@ -436,7 +473,9 @@ impl Engine { } // eval - reaching this point it must be a method-style call - KEYWORD_EVAL if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => { + KEYWORD_EVAL + if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1, pub_only) => + { Err(Box::new(EvalAltResult::ErrorRuntime( "'eval' should not be called in method style. Try eval(...);".into(), Position::none(), @@ -449,7 +488,7 @@ impl Engine { let mut mods = Imports::new(); self.call_fn_raw( &mut scope, &mut mods, state, lib, fn_name, hashes, args, is_ref, is_method, - def_val, level, + pub_only, def_val, level, ) } } @@ -499,28 +538,28 @@ impl Engine { } /// Call a dot method. + /// Position in `EvalAltResult` is `None` and must be set afterwards. #[cfg(not(feature = "no_object"))] pub(crate) fn make_method_call( &self, state: &mut State, lib: &Module, + name: &str, + hash: u64, target: &mut Target, - expr: &Expr, idx_val: Dynamic, + def_val: Option, + native: bool, + pub_only: bool, level: usize, ) -> Result<(Dynamic, bool), Box> { - let ((name, native, pos), _, hash, _, def_val) = match expr { - Expr::FnCall(x) => x.as_ref(), - _ => unreachable!(), - }; - let is_ref = target.is_ref(); let is_value = target.is_value(); // Get a reference to the mutation target Dynamic let obj = target.as_mut(); let mut idx = idx_val.cast::>(); - let mut _fn_name = name.as_ref(); + let mut _fn_name = name; let (result, updated) = if _fn_name == KEYWORD_FN_PTR_CALL && obj.is::() { // FnPtr call @@ -539,7 +578,7 @@ impl Engine { // Map it to name(args) in function-call style self.exec_fn_call( - state, lib, fn_name, *native, hash, args, false, false, *def_val, level, + state, lib, fn_name, native, hash, args, false, false, pub_only, def_val, level, ) } else if _fn_name == KEYWORD_FN_PTR_CALL && idx.len() > 0 && idx[0].is::() { // FnPtr call on object @@ -558,7 +597,7 @@ impl Engine { // Map it to name(args) in function-call style self.exec_fn_call( - state, lib, &fn_name, *native, hash, args, is_ref, true, *def_val, level, + state, lib, &fn_name, native, hash, args, is_ref, true, pub_only, def_val, level, ) } else if _fn_name == KEYWORD_FN_PTR_CURRY && obj.is::() { // Curry call @@ -579,7 +618,7 @@ impl Engine { } else { #[cfg(not(feature = "no_object"))] let redirected; - let mut _hash = *hash; + let mut _hash = hash; // Check if it is a map method call in OOP style #[cfg(not(feature = "no_object"))] @@ -600,10 +639,9 @@ impl Engine { let args = arg_values.as_mut(); self.exec_fn_call( - state, lib, _fn_name, *native, _hash, args, is_ref, true, *def_val, level, + state, lib, _fn_name, native, _hash, args, is_ref, true, pub_only, def_val, level, ) - } - .map_err(|err| err.new_position(*pos))?; + }?; // Feed the changed temp value back if updated && !is_ref && !is_value { @@ -628,13 +666,14 @@ impl Engine { def_val: Option, mut hash: u64, native: bool, + pub_only: bool, level: usize, ) -> Result> { // Handle Fn() if name == KEYWORD_FN_PTR && args_expr.len() == 1 { let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::())); - if !self.has_override(lib, hash_fn, hash) { + if !self.has_override(lib, hash_fn, hash, pub_only) { // Fn - only in function call style let expr = args_expr.get(0).unwrap(); let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; @@ -685,7 +724,7 @@ impl Engine { if name == KEYWORD_EVAL && args_expr.len() == 1 { let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::())); - if !self.has_override(lib, hash_fn, hash) { + if !self.has_override(lib, hash_fn, hash, pub_only) { // eval - only in function call style let prev_len = scope.len(); let expr = args_expr.get(0).unwrap(); @@ -710,7 +749,10 @@ impl Engine { let mut curry: StaticVec<_> = Default::default(); let mut name = name; - if name == KEYWORD_FN_PTR_CALL && args_expr.len() >= 1 && !self.has_override(lib, 0, hash) { + if name == KEYWORD_FN_PTR_CALL + && args_expr.len() >= 1 + && !self.has_override(lib, 0, hash, pub_only) + { let expr = args_expr.get(0).unwrap(); let fn_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; @@ -779,7 +821,7 @@ impl Engine { let args = args.as_mut(); self.exec_fn_call( - state, lib, name, native, hash, args, is_ref, false, def_val, level, + state, lib, name, native, hash, args, is_ref, false, pub_only, def_val, level, ) .map(|(v, _)| v) } @@ -798,6 +840,7 @@ impl Engine { args_expr: &[Expr], def_val: Option, hash_script: u64, + pub_only: bool, level: usize, ) -> Result> { let modules = modules.as_ref().unwrap(); @@ -844,9 +887,15 @@ impl Engine { let module = search_imports(mods, state, modules)?; // First search in script-defined functions (can override built-in) - let func = match module.get_qualified_fn(hash_script) { - Err(err) if matches!(*err, EvalAltResult::ErrorFunctionNotFound(_, _)) => { - // Then search in Rust functions + let check = if pub_only { + check_public_access + } else { + no_check_access + }; + + let func = match module.get_qualified_fn(hash_script).and_then(check) { + // Then search in Rust functions + None => { self.inc_operations(state)?; // Qualified Rust functions are indexed in two steps: @@ -858,14 +907,14 @@ impl Engine { // 3) The final hash is the XOR of the two hashes. let hash_qualified_fn = hash_script ^ hash_fn_args; - module.get_qualified_fn(hash_qualified_fn) + module.get_qualified_fn(hash_qualified_fn).and_then(check) } r => r, }; match func { #[cfg(not(feature = "no_function"))] - Ok(f) if f.is_script() => { + Some(f) if f.is_script() => { let args = args.as_mut(); let fn_def = f.get_fn_def(); let mut scope = Scope::new(); @@ -874,31 +923,24 @@ impl Engine { &mut scope, &mut mods, state, lib, &mut None, name, fn_def, args, level, ) } - Ok(f) => f.get_native_fn()(self, lib, args.as_mut()), - Err(err) => match *err { - EvalAltResult::ErrorFunctionNotFound(_, _) if def_val.is_some() => { - Ok(def_val.unwrap().into()) - } - EvalAltResult::ErrorFunctionNotFound(_, pos) => { - Err(Box::new(EvalAltResult::ErrorFunctionNotFound( - format!( - "{}{} ({})", - modules, - name, - args.iter() - .map(|a| if a.is::() { - "&str | ImmutableString | String" - } else { - self.map_type_name((*a).type_name()) - }) - .collect::>() - .join(", ") - ), - pos, - ))) - } - _ => Err(err), - }, + Some(f) => f.get_native_fn()(self, lib, args.as_mut()), + None if def_val.is_some() => Ok(def_val.unwrap().into()), + None => Err(Box::new(EvalAltResult::ErrorFunctionNotFound( + format!( + "{}{} ({})", + modules, + name, + args.iter() + .map(|a| if a.is::() { + "&str | ImmutableString | String" + } else { + self.map_type_name((*a).type_name()) + }) + .collect::>() + .join(", ") + ), + Position::none(), + ))), } } } diff --git a/src/fn_native.rs b/src/fn_native.rs index bfb8b956..22919274 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -1,16 +1,18 @@ //! Module defining interfaces to native-Rust functions. use crate::any::Dynamic; +use crate::calc_fn_hash; use crate::engine::Engine; use crate::module::Module; +use crate::parser::FnAccess; use crate::result::EvalAltResult; use crate::token::{is_valid_identifier, Position}; use crate::utils::ImmutableString; #[cfg(not(feature = "no_function"))] -use crate::{module::FuncReturn, parser::ScriptFnDef, scope::Scope, utils::StaticVec}; +use crate::{module::FuncReturn, parser::ScriptFnDef, utils::StaticVec}; -use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, string::String, vec::Vec}; +use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, iter::empty, string::String, vec::Vec}; #[cfg(not(feature = "no_function"))] use crate::stdlib::mem; @@ -89,9 +91,7 @@ impl FnPtr { /// Call the function pointer with curried arguments (if any). /// - /// The function must be a script-defined function. It cannot be a Rust function. - /// - /// To call a Rust function, just call it directly in Rust! + /// If this function is a script-defined function, it must not be marked private. /// /// ## WARNING /// @@ -107,14 +107,39 @@ impl FnPtr { this_ptr: Option<&mut Dynamic>, mut arg_values: impl AsMut<[Dynamic]>, ) -> FuncReturn { - let args = self + let mut args_data = self .1 .iter() .cloned() .chain(arg_values.as_mut().iter_mut().map(|v| mem::take(v))) .collect::>(); - engine.call_fn_dynamic(&mut Scope::new(), lib, self.0.as_str(), this_ptr, args) + let has_this = this_ptr.is_some(); + let args_len = args_data.len(); + let mut args = args_data.iter_mut().collect::>(); + + if let Some(obj) = this_ptr { + args.insert(0, obj); + } + + let fn_name = self.0.as_str(); + let hash_script = calc_fn_hash(empty(), fn_name, args_len, empty()); + + engine + .exec_fn_call( + &mut Default::default(), + lib.as_ref(), + fn_name, + false, + hash_script, + args.as_mut(), + has_this, + has_this, + true, + None, + 0, + ) + .map(|(v, _)| v) } } @@ -255,6 +280,15 @@ impl CallableFunction { Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => false, } } + /// Get the access mode. + pub fn access(&self) -> FnAccess { + match self { + CallableFunction::Pure(_) + | CallableFunction::Method(_) + | CallableFunction::Iterator(_) => FnAccess::Public, + CallableFunction::Script(f) => f.access, + } + } /// Get a reference to a native Rust function. /// /// # Panics diff --git a/src/module.rs b/src/module.rs index 07430f6e..9fad5571 100644 --- a/src/module.rs +++ b/src/module.rs @@ -912,16 +912,8 @@ impl Module { /// /// The `u64` hash is calculated by the function `crate::calc_fn_hash` and must match /// the hash calculated by `index_all_sub_modules`. - pub(crate) fn get_qualified_fn( - &self, - hash_qualified_fn: u64, - ) -> Result<&Func, Box> { - self.all_functions.get(&hash_qualified_fn).ok_or_else(|| { - Box::new(EvalAltResult::ErrorFunctionNotFound( - String::new(), - Position::none(), - )) - }) + pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&Func> { + self.all_functions.get(&hash_qualified_fn) } /// Merge another module into this module. diff --git a/src/optimize.rs b/src/optimize.rs index 0948fbd5..bf5f6cd3 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -141,6 +141,7 @@ fn call_fn_with_constant_arguments( arg_values.iter_mut().collect::>().as_mut(), false, false, + true, None, 0, ) diff --git a/src/parser.rs b/src/parser.rs index 51685a6c..1177c3c2 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2674,10 +2674,9 @@ fn parse_block( // Parse statements inside the block settings.is_global = false; - let stmt = if let Some(s) = parse_stmt(input, state, lib, settings.level_up())? { - s - } else { - continue; + let stmt = match parse_stmt(input, state, lib, settings.level_up())? { + Some(s) => s, + None => continue, }; // See if it needs a terminating semicolon @@ -3137,10 +3136,9 @@ impl Engine { pos: Position::none(), }; - let stmt = if let Some(s) = parse_stmt(input, &mut state, &mut functions, settings)? { - s - } else { - continue; + let stmt = match parse_stmt(input, &mut state, &mut functions, settings)? { + Some(s) => s, + None => continue, }; let need_semicolon = !stmt.is_self_terminated(); diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 3b93d520..e75d93ec 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -1,6 +1,7 @@ #![cfg(not(feature = "no_function"))] use rhai::{ - Dynamic, Engine, EvalAltResult, FnPtr, Func, Module, ParseError, ParseErrorType, Scope, INT, + Dynamic, Engine, EvalAltResult, FnPtr, Func, Module, ParseError, ParseErrorType, RegisterFn, + Scope, INT, }; use std::any::TypeId; @@ -119,21 +120,23 @@ fn test_anonymous_fn() -> Result<(), Box> { fn test_fn_ptr_raw() -> Result<(), Box> { let mut engine = Engine::new(); - engine.register_raw_fn( - "bar", - &[ - TypeId::of::(), - TypeId::of::(), - TypeId::of::(), - ], - move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { - let fp = std::mem::take(args[1]).cast::(); - let value = args[2].clone(); - let this_ptr = args.get_mut(0).unwrap(); + engine + .register_fn("mul", |x: &mut INT, y: INT| *x *= y) + .register_raw_fn( + "bar", + &[ + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + ], + move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { + let fp = std::mem::take(args[1]).cast::(); + let value = args[2].clone(); + let this_ptr = args.get_mut(0).unwrap(); - fp.call_dynamic(engine, lib, Some(this_ptr), [value]) - }, - ); + fp.call_dynamic(engine, lib, Some(this_ptr), [value]) + }, + ); assert_eq!( engine.eval::( @@ -148,6 +151,30 @@ fn test_fn_ptr_raw() -> Result<(), Box> { 42 ); + assert!(matches!( + *engine.eval::( + r#" + private fn foo(x) { this += x; } + + let x = 41; + x.bar(Fn("foo"), 1); + x + "# + ).expect_err("should error"), + EvalAltResult::ErrorFunctionNotFound(x, _) if x.starts_with("foo (") + )); + + assert_eq!( + engine.eval::( + r#" + let x = 21; + x.bar(Fn("mul"), 2); + x + "# + )?, + 42 + ); + Ok(()) } From 057f6435a45aa1160faf0cd3c62956b7d6505f11 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 27 Jul 2020 18:10:45 +0800 Subject: [PATCH 07/64] Add public_only parameter to module function methods. --- RELEASES.md | 1 + src/engine.rs | 30 ++++++++--------- src/fn_call.rs | 80 ++++++++++++++------------------------------- src/module.rs | 58 ++++++++++++++++++++------------ src/packages/mod.rs | 9 ++--- src/result.rs | 2 +- tests/modules.rs | 2 +- tests/time.rs | 5 ++- 8 files changed, 87 insertions(+), 100 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 520a2bf1..bd4623c0 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -27,6 +27,7 @@ Breaking changes * Language keywords are now _reserved_ (even when disabled) and they can no longer be used as variable names. * Function signature for defining custom syntax is simplified. * `Engine::register_raw_fn_XXX` API shortcuts are removed. +* `PackagesCollection::get_fn`, `PackagesCollection::contains_fn`, `Module::get_fn` and `Module::contains_fn` now take an additional `public_only` parameter indicating whether only public functions are accepted. Housekeeping ------------ diff --git a/src/engine.rs b/src/engine.rs index aea01500..5eb11702 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -19,7 +19,7 @@ use crate::utils::StaticVec; use crate::any::Variant; #[cfg(not(feature = "no_function"))] -use crate::parser::{FnAccess, ScriptFnDef}; +use crate::parser::ScriptFnDef; #[cfg(not(feature = "no_module"))] use crate::module::ModuleResolver; @@ -264,19 +264,15 @@ pub fn get_script_function_by_signature<'a>( module: &'a Module, name: &str, params: usize, - public_only: bool, + pub_only: bool, ) -> Option<&'a ScriptFnDef> { // Qualifiers (none) + function name + number of arguments. let hash_script = calc_fn_hash(empty(), name, params, empty()); - let func = module.get_fn(hash_script)?; - if !func.is_script() { - return None; - } - let fn_def = func.get_fn_def(); - - match fn_def.access { - FnAccess::Private if public_only => None, - FnAccess::Private | FnAccess::Public => Some(&fn_def), + let func = module.get_fn(hash_script, pub_only)?; + if func.is_script() { + Some(func.get_fn_def()) + } else { + None } } @@ -798,7 +794,7 @@ impl Engine { let mut val = match sub_lhs { Expr::Property(p) => { - let ((prop, _, _), _) = p.as_ref(); + let ((prop, _, _), pos) = p.as_ref(); let index = prop.clone().into(); self.get_indexed_mut(state, lib, target, index, *pos, false, level)? } @@ -827,12 +823,12 @@ impl Engine { } // xxx.sub_lhs[expr] | xxx.sub_lhs.expr Expr::Index(x) | Expr::Dot(x) => { - let (sub_lhs, expr, pos) = x.as_ref(); + let (sub_lhs, expr, _) = x.as_ref(); match sub_lhs { // xxx.prop[expr] | xxx.prop.expr Expr::Property(p) => { - let ((_, getter, setter), _) = p.as_ref(); + let ((_, getter, setter), pos) = p.as_ref(); let arg_values = &mut [target.as_mut(), &mut Default::default()]; let args = &mut arg_values[..1]; let (mut val, updated) = self @@ -1304,8 +1300,8 @@ impl Engine { if let Some(CallableFunction::Method(func)) = self .global_module - .get_fn(hash_fn) - .or_else(|| self.packages.get_fn(hash_fn)) + .get_fn(hash_fn, false) + .or_else(|| self.packages.get_fn(hash_fn, false)) { // Overriding exact implementation func(self, lib, &mut [lhs_ptr, &mut rhs_val])?; @@ -1431,7 +1427,7 @@ impl Engine { let ((name, _, pos), modules, hash, args_expr, def_val) = x.as_ref(); self.make_qualified_function_call( scope, mods, state, lib, this_ptr, modules, name, args_expr, *def_val, *hash, - true, level, + level, ) .map_err(|err| err.new_position(*pos)) } diff --git a/src/fn_call.rs b/src/fn_call.rs index d581268a..6bd95393 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -8,10 +8,10 @@ use crate::engine::{ KEYWORD_TYPE_OF, }; use crate::error::ParseErrorType; -use crate::fn_native::{CallableFunction, FnCallArgs, FnPtr}; +use crate::fn_native::{FnCallArgs, FnPtr}; use crate::module::{Module, ModuleRef}; use crate::optimize::OptimizationLevel; -use crate::parser::{Expr, FnAccess, ImmutableString, AST, INT}; +use crate::parser::{Expr, ImmutableString, AST, INT}; use crate::result::EvalAltResult; use crate::scope::Scope; use crate::token::Position; @@ -105,19 +105,6 @@ fn restore_first_arg<'a>(old_this_ptr: Option<&'a mut Dynamic>, args: &mut FnCal } } -#[inline] -fn check_public_access(func: &CallableFunction) -> Option<&CallableFunction> { - if func.access() == FnAccess::Private { - None - } else { - Some(func) - } -} -#[inline(always)] -fn no_check_access(func: &CallableFunction) -> Option<&CallableFunction> { - Some(func) -} - impl Engine { /// Universal method for calling functions either registered with the `Engine` or written in Rhai. /// Position in `EvalAltResult` is `None` and must be set afterwards. @@ -146,12 +133,6 @@ impl Engine { let native_only = hash_script == 0; - let check = if pub_only { - check_public_access - } else { - no_check_access - }; - // Check for stack overflow #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "unchecked"))] @@ -170,16 +151,14 @@ impl Engine { // Then search packages // NOTE: We skip script functions for global_module and packages, and native functions for lib let func = if !native_only { - lib.get_fn(hash_script).and_then(check) //.or_else(|| lib.get_fn(hash_fn)).and_then(check) + lib.get_fn(hash_script, pub_only) //.or_else(|| lib.get_fn(hash_fn, pub_only)) } else { None } - //.or_else(|| self.global_module.get_fn(hash_script)).and_then(check) - .or_else(|| self.global_module.get_fn(hash_fn)) - .and_then(check) - //.or_else(|| self.packages.get_fn(hash_script)).and_then(check) - .or_else(|| self.packages.get_fn(hash_fn)) - .and_then(check); + //.or_else(|| self.global_module.get_fn(hash_script, pub_only)) + .or_else(|| self.global_module.get_fn(hash_fn, pub_only)) + //.or_else(|| self.packages.get_fn(hash_script, pub_only)) + .or_else(|| self.packages.get_fn(hash_fn, pub_only)); if let Some(func) = func { #[cfg(not(feature = "no_function"))] @@ -274,7 +253,11 @@ impl Engine { // Getter function not found? if let Some(prop) = extract_prop_from_getter(fn_name) { return Err(Box::new(EvalAltResult::ErrorDotExpr( - format!("- property '{}' unknown or write-only", prop), + format!( + "Unknown property '{}' for {}, or it is write-only", + prop, + self.map_type_name(args[0].type_name()) + ), Position::none(), ))); } @@ -282,7 +265,11 @@ impl Engine { // Setter function not found? if let Some(prop) = extract_prop_from_setter(fn_name) { return Err(Box::new(EvalAltResult::ErrorDotExpr( - format!("- property '{}' unknown or read-only", prop), + format!( + "Unknown property '{}' for {}, or it is read-only", + prop, + self.map_type_name(args[0].type_name()) + ), Position::none(), ))); } @@ -401,27 +388,17 @@ impl Engine { // Has a system function an override? fn has_override(&self, lib: &Module, hash_fn: u64, hash_script: u64, pub_only: bool) -> bool { - let check = if pub_only { - check_public_access - } else { - no_check_access - }; - // NOTE: We skip script functions for global_module and packages, and native functions for lib // First check script-defined functions - lib.get_fn(hash_script) - .and_then(check) - //.or_else(|| lib.get_fn(hash_fn)).and_then(check) + lib.contains_fn(hash_script, pub_only) + //|| lib.contains_fn(hash_fn, pub_only) // Then check registered functions - //.or_else(|| self.global_module.get_fn(hash_script)).and_then(check) - .or_else(|| self.global_module.get_fn(hash_fn)) - .and_then(check) + //|| self.global_module.contains_fn(hash_script, pub_only) + || self.global_module.contains_fn(hash_fn, pub_only) // Then check packages - //.or_else(|| self.packages.get_fn(hash_script)).and_then(check) - .or_else(|| self.packages.get_fn(hash_fn)) - .and_then(check) - .is_some() + //|| self.packages.contains_fn(hash_script, pub_only) + || self.packages.contains_fn(hash_fn, pub_only) } /// Perform an actual function call, taking care of special functions @@ -840,7 +817,6 @@ impl Engine { args_expr: &[Expr], def_val: Option, hash_script: u64, - pub_only: bool, level: usize, ) -> Result> { let modules = modules.as_ref().unwrap(); @@ -887,13 +863,7 @@ impl Engine { let module = search_imports(mods, state, modules)?; // First search in script-defined functions (can override built-in) - let check = if pub_only { - check_public_access - } else { - no_check_access - }; - - let func = match module.get_qualified_fn(hash_script).and_then(check) { + let func = match module.get_qualified_fn(hash_script) { // Then search in Rust functions None => { self.inc_operations(state)?; @@ -907,7 +877,7 @@ impl Engine { // 3) The final hash is the XOR of the two hashes. let hash_qualified_fn = hash_script ^ hash_fn_args; - module.get_qualified_fn(hash_qualified_fn).and_then(check) + module.get_qualified_fn(hash_qualified_fn) } r => r, }; diff --git a/src/module.rs b/src/module.rs index 9fad5571..b88cbc59 100644 --- a/src/module.rs +++ b/src/module.rs @@ -355,10 +355,20 @@ impl Module { /// /// let mut module = Module::new(); /// let hash = module.set_fn_0("calc", || Ok(42_i64)); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` - pub fn contains_fn(&self, hash_fn: u64) -> bool { - self.functions.contains_key(&hash_fn) + pub fn contains_fn(&self, hash_fn: u64, public_only: bool) -> bool { + if public_only { + self.functions + .get(&hash_fn) + .map(|(_, access, _, _)| match access { + FnAccess::Public => true, + FnAccess::Private => false, + }) + .unwrap_or(false) + } else { + self.functions.contains_key(&hash_fn) + } } /// Set a Rust function into the module, returning a hash key. @@ -443,7 +453,7 @@ impl Module { /// Ok(orig) // return Result> /// }); /// - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` pub fn set_raw_fn( &mut self, @@ -468,7 +478,7 @@ impl Module { /// /// let mut module = Module::new(); /// let hash = module.set_fn_0("calc", || Ok(42_i64)); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` pub fn set_fn_0( &mut self, @@ -491,7 +501,7 @@ impl Module { /// /// let mut module = Module::new(); /// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1)); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` pub fn set_fn_1( &mut self, @@ -516,7 +526,7 @@ impl Module { /// /// let mut module = Module::new(); /// let hash = module.set_fn_1_mut("calc", |x: &mut i64| { *x += 1; Ok(*x) }); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` pub fn set_fn_1_mut( &mut self, @@ -541,7 +551,7 @@ impl Module { /// /// let mut module = Module::new(); /// let hash = module.set_getter_fn("value", |x: &mut i64| { Ok(*x) }); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` #[cfg(not(feature = "no_object"))] pub fn set_getter_fn( @@ -565,7 +575,7 @@ impl Module { /// let hash = module.set_fn_2("calc", |x: i64, y: ImmutableString| { /// Ok(x + y.len() as i64) /// }); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` pub fn set_fn_2( &mut self, @@ -596,7 +606,7 @@ impl Module { /// let hash = module.set_fn_2_mut("calc", |x: &mut i64, y: ImmutableString| { /// *x += y.len() as i64; Ok(*x) /// }); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` pub fn set_fn_2_mut( &mut self, @@ -628,7 +638,7 @@ impl Module { /// *x = y.len() as i64; /// Ok(()) /// }); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` #[cfg(not(feature = "no_object"))] pub fn set_setter_fn( @@ -653,7 +663,7 @@ impl Module { /// let hash = module.set_indexer_get_fn(|x: &mut i64, y: ImmutableString| { /// Ok(*x + y.len() as i64) /// }); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] @@ -677,7 +687,7 @@ impl Module { /// let hash = module.set_fn_3("calc", |x: i64, y: ImmutableString, z: i64| { /// Ok(x + y.len() as i64 + z) /// }); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` pub fn set_fn_3< A: Variant + Clone, @@ -714,7 +724,7 @@ impl Module { /// let hash = module.set_fn_3_mut("calc", |x: &mut i64, y: ImmutableString, z: i64| { /// *x += y.len() as i64 + z; Ok(*x) /// }); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` pub fn set_fn_3_mut< A: Variant + Clone, @@ -752,7 +762,7 @@ impl Module { /// *x = y.len() as i64 + value; /// Ok(()) /// }); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] @@ -796,8 +806,8 @@ impl Module { /// Ok(()) /// } /// ); - /// assert!(module.contains_fn(hash_get)); - /// assert!(module.contains_fn(hash_set)); + /// assert!(module.contains_fn(hash_get, true)); + /// assert!(module.contains_fn(hash_set, true)); /// ``` #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] @@ -825,7 +835,7 @@ impl Module { /// let hash = module.set_fn_4("calc", |x: i64, y: ImmutableString, z: i64, _w: ()| { /// Ok(x + y.len() as i64 + z) /// }); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` pub fn set_fn_4< A: Variant + Clone, @@ -869,7 +879,7 @@ impl Module { /// let hash = module.set_fn_4_mut("calc", |x: &mut i64, y: ImmutableString, z: i64, _w: ()| { /// *x += y.len() as i64 + z; Ok(*x) /// }); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` pub fn set_fn_4_mut< A: Variant + Clone, @@ -903,8 +913,14 @@ impl Module { /// /// The `u64` hash is calculated by the function `crate::calc_fn_hash`. /// It is also returned by the `set_fn_XXX` calls. - pub(crate) fn get_fn(&self, hash_fn: u64) -> Option<&Func> { - self.functions.get(&hash_fn).map(|(_, _, _, v)| v) + pub(crate) fn get_fn(&self, hash_fn: u64, public_only: bool) -> Option<&Func> { + self.functions + .get(&hash_fn) + .and_then(|(_, access, _, f)| match access { + _ if !public_only => Some(f), + FnAccess::Public => Some(f), + FnAccess::Private => None, + }) } /// Get a modules-qualified function. diff --git a/src/packages/mod.rs b/src/packages/mod.rs index ccd78add..1c6dc3a9 100644 --- a/src/packages/mod.rs +++ b/src/packages/mod.rs @@ -61,14 +61,15 @@ impl PackagesCollection { self.0.insert(0, package); } /// Does the specified function hash key exist in the `PackagesCollection`? - pub fn contains_fn(&self, hash: u64) -> bool { - self.0.iter().any(|p| p.contains_fn(hash)) + #[allow(dead_code)] + pub fn contains_fn(&self, hash: u64, public_only: bool) -> bool { + self.0.iter().any(|p| p.contains_fn(hash, public_only)) } /// Get specified function via its hash key. - pub fn get_fn(&self, hash: u64) -> Option<&CallableFunction> { + pub fn get_fn(&self, hash: u64, public_only: bool) -> Option<&CallableFunction> { self.0 .iter() - .map(|p| p.get_fn(hash)) + .map(|p| p.get_fn(hash, public_only)) .find(|f| f.is_some()) .flatten() } diff --git a/src/result.rs b/src/result.rs index 66fd9cdf..550dcc37 100644 --- a/src/result.rs +++ b/src/result.rs @@ -182,7 +182,7 @@ impl fmt::Display for EvalAltResult { | Self::ErrorVariableNotFound(s, _) | Self::ErrorModuleNotFound(s, _) => write!(f, "{}: '{}'", desc, s)?, - Self::ErrorDotExpr(s, _) if !s.is_empty() => write!(f, "{} {}", desc, s)?, + Self::ErrorDotExpr(s, _) if !s.is_empty() => write!(f, "{}", s)?, Self::ErrorIndexingType(_, _) | Self::ErrorNumericIndexExpr(_) diff --git a/tests/modules.rs b/tests/modules.rs index fd049815..3b00ab17 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -35,7 +35,7 @@ fn test_module_sub_module() -> Result<(), Box> { let m2 = m.get_sub_module("universe").unwrap(); assert!(m2.contains_var("answer")); - assert!(m2.contains_fn(hash_inc)); + assert!(m2.contains_fn(hash_inc, false)); assert_eq!(m2.get_var_value::("answer").unwrap(), 41); diff --git a/tests/time.rs b/tests/time.rs index 9873decf..b3f6668f 100644 --- a/tests/time.rs +++ b/tests/time.rs @@ -1,11 +1,14 @@ #![cfg(not(feature = "no_std"))] #![cfg(not(target_arch = "wasm32"))] -use rhai::{Engine, EvalAltResult, INT}; +use rhai::{Engine, EvalAltResult}; #[cfg(not(feature = "no_float"))] use rhai::FLOAT; +#[cfg(feature = "no_float")] +use rhai::INT; + #[test] fn test_timestamp() -> Result<(), Box> { let engine = Engine::new(); From f05cd1fdf36e0f67541223d7530b8ab4f9ca7ff0 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 28 Jul 2020 10:25:26 +0800 Subject: [PATCH 08/64] Add shared and sync to reserved keywords. --- doc/src/appendix/keywords.md | 2 ++ doc/src/language/keywords.md | 30 +++++++++++++++--------------- src/token.rs | 4 ++-- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/doc/src/appendix/keywords.md b/doc/src/appendix/keywords.md index f7c1be73..5877f1a0 100644 --- a/doc/src/appendix/keywords.md +++ b/doc/src/appendix/keywords.md @@ -59,8 +59,10 @@ Reserved Keywords | `package` | Package | | `spawn` | Threading | | `go` | Threading | +| `shared` | Threading | | `await` | Async | | `async` | Async | +| `sync` | Async | | `yield` | Async | | `default` | Special value | | `void` | Special value | diff --git a/doc/src/language/keywords.md b/doc/src/language/keywords.md index 851b7907..509823db 100644 --- a/doc/src/language/keywords.md +++ b/doc/src/language/keywords.md @@ -5,21 +5,21 @@ Keywords The following are reserved keywords in Rhai: -| Active keywords | Reserved keywords | Usage | Inactive under feature | -| ------------------------------------------------- | ---------------------------------------- | --------------------- | :--------------------: | -| `true`, `false` | | Boolean constants | | -| `let`, `const` | `var`, `static` | Variable declarations | | -| `if`, `else` | `then`, `goto`, `exit` | Control flow | | -| | `switch`, `match`, `case` | Matching | | -| `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | Looping | | -| `fn`, `private` | `public`, `new` | Functions | [`no_function`] | -| `return` | | Return values | | -| `throw` | `try`, `catch` | Throw exceptions | | -| `import`, `export`, `as` | `use`, `with`, `module`, `package` | Modules/packages | [`no_module`] | -| `Fn`, `call`, `curry` | | Function pointers | | -| | `spawn`, `go`, `async`, `await`, `yield` | Threading/async | | -| `type_of`, `print`, `debug`, `eval` | | Special functions | | -| | `default`, `void`, `null`, `nil` | Special values | | +| Active keywords | Reserved keywords | Usage | Inactive under feature | +| ------------------------------------------------- | ---------------------------------------------------------- | --------------------- | :--------------------: | +| `true`, `false` | | Boolean constants | | +| `let`, `const` | `var`, `static` | Variable declarations | | +| `if`, `else` | `then`, `goto`, `exit` | Control flow | | +| | `switch`, `match`, `case` | Matching | | +| `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | Looping | | +| `fn`, `private` | `public`, `new` | Functions | [`no_function`] | +| `return` | | Return values | | +| `throw` | `try`, `catch` | Throw exceptions | | +| `import`, `export`, `as` | `use`, `with`, `module`, `package` | Modules/packages | [`no_module`] | +| `Fn`, `call`, `curry` | | Function pointers | | +| | `spawn`, `go`, `shared`, `sync`, `async`, `await`, `yield` | Threading/async | | +| `type_of`, `print`, `debug`, `eval` | | Special functions | | +| | `default`, `void`, `null`, `nil` | Special values | | Keywords cannot become the name of a [function] or [variable], even when they are disabled. diff --git a/src/token.rs b/src/token.rs index 0aab8ce5..b8576133 100644 --- a/src/token.rs +++ b/src/token.rs @@ -495,8 +495,8 @@ impl Token { "===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "public" | "new" | "use" | "module" | "package" | "var" | "static" | "with" | "do" | "each" | "then" | "goto" | "exit" | "switch" | "match" | "case" | "try" | "catch" - | "default" | "void" | "null" | "nil" | "spawn" | "go" | "async" | "await" - | "yield" => Reserved(syntax.into()), + | "default" | "void" | "null" | "nil" | "spawn" | "go" | "shared" | "sync" + | "async" | "await" | "yield" => Reserved(syntax.into()), KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS => Reserved(syntax.into()), From b70fd35f4a89de1285160959e8d15978883628d0 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 28 Jul 2020 10:25:57 +0800 Subject: [PATCH 09/64] Revise docs. --- README.md | 5 +-- doc/src/about/features.md | 13 +++--- doc/src/language/fn-namespaces.md | 71 ++++++++++++++++++------------ doc/src/language/functions.md | 2 +- doc/src/language/method.md | 22 ++++++++- doc/src/language/modules/import.md | 15 +++++-- doc/src/language/overload.md | 10 ++--- doc/src/start/features.md | 2 +- src/module.rs | 2 +- 9 files changed, 91 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 1516abcc..7aef538c 100644 --- a/README.md +++ b/README.md @@ -27,12 +27,11 @@ Standard features * Easily [call a script-defined function](https://schungx.github.io/rhai/engine/call-fn.html) from Rust. * Fairly low compile-time overhead. * Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM). -* 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_"`). +* Relatively little `unsafe` code (yes there are some for performance reasons). * Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature). * [Function overloading](https://schungx.github.io/rhai/language/overload.html). * [Operator overloading](https://schungx.github.io/rhai/rust/operators.html). -* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html). +* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html) with additional support for [currying](https://schungx.github.io/rhai/language/fn-curry.html). * Some support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.html). * Organize code base with dynamically-loadable [modules](https://schungx.github.io/rhai/language/modules.html). * Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature). diff --git a/doc/src/about/features.md b/doc/src/about/features.md index 13b2e4c1..ffa28cdc 100644 --- a/doc/src/about/features.md +++ b/doc/src/about/features.md @@ -35,23 +35,20 @@ Dynamic * Organize code base with dynamically-loadable [modules]. -* Dynamic dispatch via [function pointers]. +* Dynamic dispatch via [function pointers] with additional support for [currying]. * Some support for [object-oriented programming (OOP)][OOP]. -* Serialization/deserialization support via [`serde`]. - Safe ---- -* 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_"`). +* 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 (e.g. via a `RefCell`). Rugged ------ -* Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`). - * 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. @@ -61,6 +58,8 @@ Flexible * Re-entrant scripting [`Engine`] can be made `Send + Sync` (via the [`sync`] feature). +* Serialization/deserialization support via [`serde`](https://crates.io/crates/serde). + * Support for [minimal builds] by excluding unneeded language [features]. * Supports [most build targets](targets.md) including `no-std` and [WASM]. diff --git a/doc/src/language/fn-namespaces.md b/doc/src/language/fn-namespaces.md index 5bd8e16f..9516f1e6 100644 --- a/doc/src/language/fn-namespaces.md +++ b/doc/src/language/fn-namespaces.md @@ -69,17 +69,22 @@ The only practical way to ensure that a function is a correct one is to use [mod i.e. define the function in a separate module and then [`import`] it: ```rust -message.rhai: +---------------- +| message.rhai | +---------------- - fn message() { "Hello!" } +fn message() { "Hello!" } -script.rhai: - fn say_hello() { - import "message" as msg; - print(msg::message()); - } - say_hello(); +--------------- +| script.rhai | +--------------- + +fn say_hello() { + import "message" as msg; + print(msg::message()); +} +say_hello(); ``` @@ -94,18 +99,23 @@ defined _within the script_. When called later, those functions will be searche current global namespace and may not be found. ```rust -greeting.rhai: +----------------- +| greeting.rhai | +----------------- - fn message() { "Hello!" }; +fn message() { "Hello!" }; - fn say_hello() { print(message()); } +fn say_hello() { print(message()); } - say_hello(); // 'message' is looked up in the global namespace +say_hello(); // 'message' is looked up in the global namespace -script.rhai: - import "greeting" as g; - g::say_hello(); // <- error: function not found - 'message' +--------------- +| script.rhai | +--------------- + +import "greeting" as g; +g::say_hello(); // <- error: function not found - 'message' ``` In the example above, although the module `greeting.rhai` loads fine (`"Hello!"` is printed), @@ -118,24 +128,29 @@ as possible and avoid cross-calling them from each other. A [function pointer] to call another function within a module-defined function: ```rust -greeting.rhai: +----------------- +| greeting.rhai | +----------------- - fn message() { "Hello!" }; +fn message() { "Hello!" }; - fn say_hello(msg_func) { // 'msg_func' is a function pointer - print(msg_func.call()); // call via the function pointer - } +fn say_hello(msg_func) { // 'msg_func' is a function pointer + print(msg_func.call()); // call via the function pointer +} - say_hello(); // 'message' is looked up in the global namespace +say_hello(); // 'message' is looked up in the global namespace -script.rhai: - import "greeting" as g; +--------------- +| script.rhai | +--------------- - fn my_msg() { - import "greeting" as g; // <- must import again here... - g::message() // <- ... otherwise will not find module 'g' - } +import "greeting" as g; - g::say_hello(Fn("my_msg")); // prints 'Hello!' +fn my_msg() { + import "greeting" as g; // <- must import again here... + g::message() // <- ... otherwise will not find module 'g' +} + +g::say_hello(Fn("my_msg")); // prints 'Hello!' ``` diff --git a/doc/src/language/functions.md b/doc/src/language/functions.md index b6c0a85f..d8f67502 100644 --- a/doc/src/language/functions.md +++ b/doc/src/language/functions.md @@ -16,7 +16,7 @@ fn sub(x, y,) { // trailing comma in parameters list is OK add(2, 3) == 5; -sub(2, 3,) == -1; // trailing comma in arguments list is OK +sub(2, 3,) == -1; // trailing comma in arguments list is OK ``` diff --git a/doc/src/language/method.md b/doc/src/language/method.md index c2e9e99e..290d1a06 100644 --- a/doc/src/language/method.md +++ b/doc/src/language/method.md @@ -37,12 +37,30 @@ array[0].update(); // <- call in method-call style will update 'a' ``` -`&mut` is Efficient ------------------- +Number of Parameters +-------------------- + +Native Rust methods registered with an [`Engine`] take _one additional parameter_ more than +an equivalent method coded in script, where the object is accessed via the `this` pointer instead. + +The following table illustrates the differences: + +| Function type | Parameters | Object reference | Function signature | +| :-----------: | :--------: | :--------------------: | :-----------------------------------------------------: | +| Native Rust | _n_ + 1 | First `&mut` parameter | `fn method`
`(obj: &mut T, x: U, y: V) {}` | +| Rhai script | _n_ | `this` | `fn method(x, y) {}` | + + +`&mut` is Efficient (Except for `ImmutableString`) +------------------------------------------------ Using a `&mut` first parameter is highly encouraged when using types that are expensive to clone, even when the intention is not to mutate that argument, because it avoids cloning that argument value. +For example, the `len` method of an [array] has the signature: `Fn(&mut Array) -> INT`. +The array itself is not modified in any way, but using a `&mut` parameter avoids a cloning that would +otherwise have happened if the signature were `Fn(Array) -> INT`. + For primary types that are cheap to clone (e.g. those that implement `Copy`), including `ImmutableString`, this is not necessary. diff --git a/doc/src/language/modules/import.md b/doc/src/language/modules/import.md index dc94b5a8..5e0a377a 100644 --- a/doc/src/language/modules/import.md +++ b/doc/src/language/modules/import.md @@ -63,7 +63,9 @@ cause a stack overflow in the [`Engine`], unless stopped by setting a limit for For instance, importing itself always causes an infinite recursion: ```rust -// This file is 'hello.rhai' +-------------- +| hello.rhai | +-------------- import "hello" as foo; // import itself - infinite recursion! @@ -73,11 +75,18 @@ foo::do_something(); Modules cross-referencing also cause infinite recursion: ```rust -// This file is 'hello.rhai' - references 'world.rhai' +-------------- +| hello.rhai | +-------------- + import "world" as foo; foo::do_something(); -// This file is 'world.rhai' - references 'hello.rhai' + +-------------- +| world.rhai | +-------------- + import "hello" as bar; bar::do_something_else(); ``` diff --git a/doc/src/language/overload.md b/doc/src/language/overload.md index bb291ca3..0b6b4ea8 100644 --- a/doc/src/language/overload.md +++ b/doc/src/language/overload.md @@ -9,15 +9,15 @@ and _number_ of parameters, but not parameter _types_ since all parameters are t New definitions _overwrite_ previous definitions of the same name and number of parameters. ```rust -fn foo(x,y,z) { print("Three!!! " + x + "," + y + "," + z) } +fn foo(x,y,z) { print("Three!!! " + x + "," + y + "," + z); } -fn foo(x) { print("One! " + x) } +fn foo(x) { print("One! " + x); } -fn foo(x,y) { print("Two! " + x + "," + y) } +fn foo(x,y) { print("Two! " + x + "," + y); } -fn foo() { print("None.") } +fn foo() { print("None."); } -fn foo(x) { print("HA! NEW ONE! " + x) } // overwrites previous definition +fn foo(x) { print("HA! NEW ONE! " + x); } // overwrites previous definition foo(1,2,3); // prints "Three!!! 1,2,3" diff --git a/doc/src/start/features.md b/doc/src/start/features.md index f2286a38..0d31d077 100644 --- a/doc/src/start/features.md +++ b/doc/src/start/features.md @@ -24,7 +24,7 @@ more control over what a script can (or cannot) do. | `no_function` | Disable script-defined [functions]. | | `no_module` | Disable loading external [modules]. | | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | -| `serde` | Enable serialization/deserialization via [`serde`]. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. | +| `serde` | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. | | `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. | diff --git a/src/module.rs b/src/module.rs index b88cbc59..8eacde7b 100644 --- a/src/module.rs +++ b/src/module.rs @@ -432,7 +432,7 @@ impl Module { /// let mut module = Module::new(); /// let hash = module.set_raw_fn("double_or_not", /// // Pass parameter types via a slice with TypeId's - /// &[std::any::TypeId::of::(), std::any::TypeId::of::() ], + /// &[std::any::TypeId::of::(), std::any::TypeId::of::()], /// // Fixed closure signature /// |engine, lib, args| { /// // 'args' is guaranteed to be the right length and of the correct types From b63ff56e096f73eacc28d6cc7eaefda6d25af9fe Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 28 Jul 2020 10:26:20 +0800 Subject: [PATCH 10/64] Make sure we keep the starting position of each statement (for future uses). --- src/engine.rs | 66 +++++++------------- src/optimize.rs | 45 ++++++++----- src/parser.rs | 163 +++++++++++++++++++++++++++++++++--------------- 3 files changed, 162 insertions(+), 112 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 5eb11702..e8cf5c3c 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -228,11 +228,6 @@ impl> From for Target<'_> { /// [INTERNALS] A type that holds all the current states of the Engine. /// Exported under the `internals` feature only. /// -/// # Safety -/// -/// This type uses some unsafe code, mainly for avoiding cloning of local variable names via -/// direct lifetime casting. -/// /// ## WARNING /// /// This type is volatile and may change. @@ -1495,6 +1490,12 @@ impl Engine { } /// Evaluate a statement + /// + /// + /// # Safety + /// + /// This method uses some unsafe code, mainly for avoiding cloning of local variable names via + /// direct lifetime casting. pub(crate) fn eval_stmt( &self, scope: &mut Scope, @@ -1538,7 +1539,7 @@ impl Engine { // If-else statement Stmt::IfThenElse(x) => { - let (expr, if_block, else_block) = x.as_ref(); + let (expr, if_block, else_block, _) = x.as_ref(); self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? .as_bool() @@ -1556,7 +1557,7 @@ impl Engine { // While loop Stmt::While(x) => loop { - let (expr, body) = x.as_ref(); + let (expr, body, _) = x.as_ref(); match self .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? @@ -1582,8 +1583,8 @@ impl Engine { }, // Loop statement - Stmt::Loop(body) => loop { - match self.eval_stmt(scope, mods, state, lib, this_ptr, body, level) { + Stmt::Loop(x) => loop { + match self.eval_stmt(scope, mods, state, lib, this_ptr, &x.0, level) { Ok(_) => (), Err(err) => match *err { EvalAltResult::ErrorLoopBreak(false, _) => (), @@ -1595,7 +1596,7 @@ impl Engine { // For loop Stmt::For(x) => { - let (name, expr, stmt) = x.as_ref(); + let (name, expr, stmt, _) = x.as_ref(); let iter_type = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; let tid = iter_type.type_id(); @@ -1641,16 +1642,9 @@ impl Engine { // Return value Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => { + let expr = x.1.as_ref().unwrap(); Err(Box::new(EvalAltResult::Return( - self.eval_expr( - scope, - mods, - state, - lib, - this_ptr, - x.1.as_ref().unwrap(), - level, - )?, + self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?, (x.0).1, ))) } @@ -1662,15 +1656,8 @@ impl Engine { // Throw value Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Exception => { - let val = self.eval_expr( - scope, - mods, - state, - lib, - this_ptr, - x.1.as_ref().unwrap(), - level, - )?; + let expr = x.1.as_ref().unwrap(); + let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; Err(Box::new(EvalAltResult::ErrorRuntime( val.take_string().unwrap_or_else(|_| "".into()), (x.0).1, @@ -1686,23 +1673,16 @@ impl Engine { // Let statement Stmt::Let(x) if x.1.is_some() => { - let ((var_name, _), expr) = x.as_ref(); - let val = self.eval_expr( - scope, - mods, - state, - lib, - this_ptr, - expr.as_ref().unwrap(), - level, - )?; + let ((var_name, _), expr, _) = x.as_ref(); + let expr = expr.as_ref().unwrap(); + let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state); scope.push_dynamic_value(var_name, ScopeEntryType::Normal, val, false); Ok(Default::default()) } Stmt::Let(x) => { - let ((var_name, _), _) = x.as_ref(); + let ((var_name, _), _, _) = x.as_ref(); let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state); scope.push(var_name, ()); Ok(Default::default()) @@ -1710,7 +1690,7 @@ impl Engine { // Const statement Stmt::Const(x) if x.1.is_constant() => { - let ((var_name, _), expr) = x.as_ref(); + let ((var_name, _), expr, _) = x.as_ref(); let val = self.eval_expr(scope, mods, state, lib, this_ptr, &expr, level)?; let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state); scope.push_dynamic_value(var_name, ScopeEntryType::Constant, val, true); @@ -1723,7 +1703,7 @@ impl Engine { // Import statement #[cfg(not(feature = "no_module"))] Stmt::Import(x) => { - let (expr, (name, _pos)) = x.as_ref(); + let (expr, (name, _pos), _) = x.as_ref(); // Guard against too many modules #[cfg(not(feature = "unchecked"))] @@ -1756,8 +1736,8 @@ impl Engine { // Export statement #[cfg(not(feature = "no_module"))] - Stmt::Export(list) => { - for ((id, id_pos), rename) in list.iter() { + Stmt::Export(x) => { + for ((id, id_pos), rename) in x.0.iter() { // Mark scope variables as public if let Some(index) = scope.get_index(id).map(|(i, _)| i) { let alias = rename.as_ref().map(|(n, _)| n).unwrap_or_else(|| id); diff --git a/src/optimize.rs b/src/optimize.rs index bf5f6cd3..27f6f816 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -185,6 +185,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { optimize_expr(expr, state), optimize_stmt(x.1, state, true), None, + x.3, ))), }, // if expr { if_block } else { else_block } @@ -201,6 +202,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { Stmt::Noop(_) => None, // Noop -> no else block stmt => Some(stmt), }, + x.3, ))), }, // while expr { block } @@ -211,7 +213,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { Stmt::Noop(pos) } // while true { block } -> loop { block } - Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(x.1, state, false))), + Expr::True(_) => Stmt::Loop(Box::new((optimize_stmt(x.1, state, false), x.2))), // while expr { block } expr => match optimize_stmt(x.1, state, false) { // while expr { break; } -> { expr; } @@ -226,11 +228,11 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { Stmt::Block(Box::new((statements, pos))) } // while expr { block } - stmt => Stmt::While(Box::new((optimize_expr(expr, state), stmt))), + stmt => Stmt::While(Box::new((optimize_expr(expr, state), stmt, x.2))), }, }, // loop { block } - Stmt::Loop(block) => match optimize_stmt(*block, state, false) { + Stmt::Loop(x) => match optimize_stmt(x.0, state, false) { // loop { break; } -> Noop Stmt::Break(pos) => { // Only a single break statement @@ -238,23 +240,26 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { Stmt::Noop(pos) } // loop { block } - stmt => Stmt::Loop(Box::new(stmt)), + stmt => Stmt::Loop(Box::new((stmt, x.1))), }, // for id in expr { block } Stmt::For(x) => Stmt::For(Box::new(( x.0, optimize_expr(x.1, state), optimize_stmt(x.2, state, false), + x.3, ))), // let id = expr; - Stmt::Let(x) if x.1.is_some() => { - Stmt::Let(Box::new((x.0, Some(optimize_expr(x.1.unwrap(), state))))) - } + Stmt::Let(x) if x.1.is_some() => Stmt::Let(Box::new(( + x.0, + Some(optimize_expr(x.1.unwrap(), state)), + x.2, + ))), // let id; stmt @ Stmt::Let(_) => stmt, // import expr as id; #[cfg(not(feature = "no_module"))] - Stmt::Import(x) => Stmt::Import(Box::new((optimize_expr(x.0, state), x.1))), + Stmt::Import(x) => Stmt::Import(Box::new((optimize_expr(x.0, state), x.1, x.2))), // { block } Stmt::Block(x) => { let orig_len = x.0.len(); // Original number of statements in the block, for change detection @@ -267,7 +272,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { .map(|stmt| match stmt { // Add constant into the state Stmt::Const(v) => { - let ((name, pos), expr) = *v; + let ((name, pos), expr, _) = *v; state.push_constant(&name, expr); state.set_dirty(); Stmt::Noop(pos) // No need to keep constants @@ -367,9 +372,11 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { // expr; Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))), // return expr; - Stmt::ReturnWithVal(x) if x.1.is_some() => { - Stmt::ReturnWithVal(Box::new((x.0, Some(optimize_expr(x.1.unwrap(), state))))) - } + Stmt::ReturnWithVal(x) if x.1.is_some() => Stmt::ReturnWithVal(Box::new(( + x.0, + Some(optimize_expr(x.1.unwrap(), state)), + x.2, + ))), // All other statements - skip stmt => stmt, } @@ -412,7 +419,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { state.set_dirty(); let pos = m.1; m.0.into_iter().find(|((name, _), _)| name.as_str() == prop.as_str()) - .map(|(_, expr)| expr.set_position(pos)) + .map(|(_, mut expr)| { expr.set_position(pos); expr }) .unwrap_or_else(|| Expr::Unit(pos)) } // lhs.rhs @@ -429,7 +436,9 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // Array literal where everything is pure - promote the indexed item. // All other items can be thrown away. state.set_dirty(); - a.0.take(i.0 as usize).set_position(a.1) + let mut expr = a.0.take(i.0 as usize); + expr.set_position(a.1); + expr } // map[string] (Expr::Map(m), Expr::StringConstant(s)) if m.0.iter().all(|(_, x)| x.is_pure()) => { @@ -438,7 +447,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { state.set_dirty(); let pos = m.1; m.0.into_iter().find(|((name, _), _)| *name == s.0) - .map(|(_, expr)| expr.set_position(pos)) + .map(|(_, mut expr)| { expr.set_position(pos); expr }) .unwrap_or_else(|| Expr::Unit(pos)) } // string[int] @@ -625,7 +634,9 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { state.set_dirty(); // Replace constant with value - state.find_constant(&name).unwrap().clone().set_position(pos) + let mut expr = state.find_constant(&name).unwrap().clone(); + expr.set_position(pos); + expr } // Custom syntax @@ -687,7 +698,7 @@ fn optimize( match &stmt { Stmt::Const(v) => { // Load constants - let ((name, _), expr) = v.as_ref(); + let ((name, _), expr, _) = v.as_ref(); state.push_constant(&name, expr.clone()); stmt // Keep it in the global scope } diff --git a/src/parser.rs b/src/parser.rs index 1177c3c2..417c35fb 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -26,7 +26,6 @@ use crate::stdlib::{ fmt, format, hash::{Hash, Hasher}, iter::empty, - mem, num::NonZeroUsize, ops::Add, string::{String, ToString}, @@ -511,33 +510,38 @@ pub enum Stmt { /// No-op. Noop(Position), /// if expr { stmt } else { stmt } - IfThenElse(Box<(Expr, Stmt, Option)>), + IfThenElse(Box<(Expr, Stmt, Option, Position)>), /// while expr { stmt } - While(Box<(Expr, Stmt)>), + While(Box<(Expr, Stmt, Position)>), /// loop { stmt } - Loop(Box), + Loop(Box<(Stmt, Position)>), /// for id in expr { stmt } - For(Box<(String, Expr, Stmt)>), + For(Box<(String, Expr, Stmt, Position)>), /// let id = expr - Let(Box<((String, Position), Option)>), + Let(Box<((String, Position), Option, Position)>), /// const id = expr - Const(Box<((String, Position), Expr)>), + Const(Box<((String, Position), Expr, Position)>), /// { stmt; ... } Block(Box<(StaticVec, Position)>), - /// { stmt } + /// expr Expr(Box), /// continue Continue(Position), /// break Break(Position), /// return/throw - ReturnWithVal(Box<((ReturnType, Position), Option)>), + ReturnWithVal(Box<((ReturnType, Position), Option, Position)>), /// import expr as module #[cfg(not(feature = "no_module"))] - Import(Box<(Expr, (String, Position))>), + Import(Box<(Expr, (String, Position), Position)>), /// expr id as name, ... #[cfg(not(feature = "no_module"))] - Export(Box)>>), + Export( + Box<( + StaticVec<((String, Position), Option<(String, Position)>)>, + Position, + )>, + ), } impl Default for Stmt { @@ -555,19 +559,44 @@ impl Stmt { Stmt::Const(x) => (x.0).1, Stmt::ReturnWithVal(x) => (x.0).1, Stmt::Block(x) => x.1, - Stmt::IfThenElse(x) => x.0.position(), + Stmt::IfThenElse(x) => x.3, Stmt::Expr(x) => x.position(), - Stmt::While(x) => x.1.position(), - Stmt::Loop(x) => x.position(), - Stmt::For(x) => x.2.position(), + Stmt::While(x) => x.2, + Stmt::Loop(x) => x.1, + Stmt::For(x) => x.3, #[cfg(not(feature = "no_module"))] - Stmt::Import(x) => (x.1).1, + Stmt::Import(x) => x.2, #[cfg(not(feature = "no_module"))] - Stmt::Export(x) => (x.get(0).0).1, + Stmt::Export(x) => x.1, } } + /// Override the `Position` of this statement. + pub fn set_position(&mut self, new_pos: Position) -> &mut Self { + match self { + Stmt::Noop(pos) | Stmt::Continue(pos) | Stmt::Break(pos) => *pos = new_pos, + Stmt::Let(x) => (x.0).1 = new_pos, + Stmt::Const(x) => (x.0).1 = new_pos, + Stmt::ReturnWithVal(x) => (x.0).1 = new_pos, + Stmt::Block(x) => x.1 = new_pos, + Stmt::IfThenElse(x) => x.3 = new_pos, + Stmt::Expr(x) => { + x.set_position(new_pos); + } + Stmt::While(x) => x.2 = new_pos, + Stmt::Loop(x) => x.1 = new_pos, + Stmt::For(x) => x.3 = new_pos, + + #[cfg(not(feature = "no_module"))] + Stmt::Import(x) => x.2 = new_pos, + #[cfg(not(feature = "no_module"))] + Stmt::Export(x) => x.1 = new_pos, + } + + self + } + /// Is this statement self-terminated (i.e. no need for a semicolon terminator)? pub fn is_self_terminated(&self) -> bool { match self { @@ -602,7 +631,7 @@ impl Stmt { } Stmt::IfThenElse(x) => x.1.is_pure(), Stmt::While(x) => x.0.is_pure() && x.1.is_pure(), - Stmt::Loop(x) => x.is_pure(), + Stmt::Loop(x) => x.0.is_pure(), Stmt::For(x) => x.1.is_pure() && x.2.is_pure(), Stmt::Let(_) | Stmt::Const(_) => false, Stmt::Block(x) => x.0.iter().all(Stmt::is_pure), @@ -832,11 +861,10 @@ impl Expr { } /// Override the `Position` of the expression. - pub(crate) fn set_position(mut self, new_pos: Position) -> Self { - match &mut self { - Self::Expr(ref mut x) => { - let expr = mem::take(x); - *x = Box::new(expr.set_position(new_pos)); + pub fn set_position(&mut self, new_pos: Position) -> &mut Self { + match self { + Self::Expr(x) => { + x.set_position(new_pos); } #[cfg(not(feature = "no_float"))] @@ -2314,7 +2342,7 @@ fn ensure_not_statement_expr(input: &mut TokenStream, type_name: &str) -> Result fn ensure_not_assignment(input: &mut TokenStream) -> Result<(), ParseError> { match input.peek().unwrap() { (Token::Equals, pos) => { - return Err(PERR::BadInput("Possibly a typo of '=='?".to_string()).into_err(*pos)) + Err(PERR::BadInput("Possibly a typo of '=='?".to_string()).into_err(*pos)) } (Token::PlusAssign, pos) | (Token::MinusAssign, pos) @@ -2326,12 +2354,10 @@ fn ensure_not_assignment(input: &mut TokenStream) -> Result<(), ParseError> { | (Token::PowerOfAssign, pos) | (Token::AndAssign, pos) | (Token::OrAssign, pos) - | (Token::XOrAssign, pos) => { - return Err(PERR::BadInput( - "Expecting a boolean expression, not an assignment".to_string(), - ) - .into_err(*pos)) - } + | (Token::XOrAssign, pos) => Err(PERR::BadInput( + "Expecting a boolean expression, not an assignment".to_string(), + ) + .into_err(*pos)), _ => Ok(()), } @@ -2345,7 +2371,8 @@ fn parse_if( mut settings: ParseSettings, ) -> Result { // if ... - settings.pos = eat_token(input, Token::If); + let token_pos = eat_token(input, Token::If); + settings.pos = token_pos; #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2369,7 +2396,9 @@ fn parse_if( None }; - Ok(Stmt::IfThenElse(Box::new((guard, if_body, else_body)))) + Ok(Stmt::IfThenElse(Box::new(( + guard, if_body, else_body, token_pos, + )))) } /// Parse a while loop. @@ -2380,7 +2409,8 @@ fn parse_while( mut settings: ParseSettings, ) -> Result { // while ... - settings.pos = eat_token(input, Token::While); + let token_pos = eat_token(input, Token::While); + settings.pos = token_pos; #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2393,7 +2423,7 @@ fn parse_while( settings.is_breakable = true; let body = parse_block(input, state, lib, settings.level_up())?; - Ok(Stmt::While(Box::new((guard, body)))) + Ok(Stmt::While(Box::new((guard, body, token_pos)))) } /// Parse a loop statement. @@ -2404,7 +2434,8 @@ fn parse_loop( mut settings: ParseSettings, ) -> Result { // loop ... - settings.pos = eat_token(input, Token::Loop); + let token_pos = eat_token(input, Token::Loop); + settings.pos = token_pos; #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2413,7 +2444,7 @@ fn parse_loop( settings.is_breakable = true; let body = parse_block(input, state, lib, settings.level_up())?; - Ok(Stmt::Loop(Box::new(body))) + Ok(Stmt::Loop(Box::new((body, token_pos)))) } /// Parse a for loop. @@ -2424,7 +2455,8 @@ fn parse_for( mut settings: ParseSettings, ) -> Result { // for ... - settings.pos = eat_token(input, Token::For); + let token_pos = eat_token(input, Token::For); + settings.pos = token_pos; #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2467,7 +2499,7 @@ fn parse_for( state.stack.truncate(prev_stack_len); - Ok(Stmt::For(Box::new((name, expr, body)))) + Ok(Stmt::For(Box::new((name, expr, body, token_pos)))) } /// Parse a variable definition statement. @@ -2479,7 +2511,8 @@ fn parse_let( mut settings: ParseSettings, ) -> Result { // let/const... (specified in `var_type`) - settings.pos = input.next().unwrap().1; + let token_pos = input.next().unwrap().1; + settings.pos = token_pos; #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2503,12 +2536,16 @@ fn parse_let( // let name = expr ScopeEntryType::Normal => { state.stack.push((name.clone(), ScopeEntryType::Normal)); - Ok(Stmt::Let(Box::new(((name, pos), Some(init_value))))) + Ok(Stmt::Let(Box::new(( + (name, pos), + Some(init_value), + token_pos, + )))) } // const name = { expr:constant } ScopeEntryType::Constant if init_value.is_constant() => { state.stack.push((name.clone(), ScopeEntryType::Constant)); - Ok(Stmt::Const(Box::new(((name, pos), init_value)))) + Ok(Stmt::Const(Box::new(((name, pos), init_value, token_pos)))) } // const name = expr: error ScopeEntryType::Constant => { @@ -2520,11 +2557,15 @@ fn parse_let( match var_type { ScopeEntryType::Normal => { state.stack.push((name.clone(), ScopeEntryType::Normal)); - Ok(Stmt::Let(Box::new(((name, pos), None)))) + Ok(Stmt::Let(Box::new(((name, pos), None, token_pos)))) } ScopeEntryType::Constant => { state.stack.push((name.clone(), ScopeEntryType::Constant)); - Ok(Stmt::Const(Box::new(((name, pos), Expr::Unit(pos))))) + Ok(Stmt::Const(Box::new(( + (name, pos), + Expr::Unit(pos), + token_pos, + )))) } } } @@ -2539,7 +2580,8 @@ fn parse_import( mut settings: ParseSettings, ) -> Result { // import ... - settings.pos = eat_token(input, Token::Import); + let token_pos = eat_token(input, Token::Import); + settings.pos = token_pos; #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2569,7 +2611,12 @@ fn parse_import( }; state.modules.push(name.clone()); - Ok(Stmt::Import(Box::new((expr, (name, settings.pos))))) + + Ok(Stmt::Import(Box::new(( + expr, + (name, settings.pos), + token_pos, + )))) } /// Parse an export statement. @@ -2580,7 +2627,8 @@ fn parse_export( _lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { - settings.pos = eat_token(input, Token::Export); + let token_pos = eat_token(input, Token::Export); + settings.pos = token_pos; #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(_state.max_expr_depth)?; @@ -2640,7 +2688,7 @@ fn parse_export( }) .map_err(|(id2, pos)| PERR::DuplicatedExport(id2.to_string()).into_err(pos))?; - Ok(Stmt::Export(Box::new(exports))) + Ok(Stmt::Export(Box::new((exports, token_pos)))) } /// Parse a statement block. @@ -2827,22 +2875,32 @@ fn parse_stmt( Token::Continue | Token::Break => Err(PERR::LoopBreak.into_err(settings.pos)), Token::Return | Token::Throw => { - let return_type = match input.next().unwrap() { - (Token::Return, _) => ReturnType::Return, - (Token::Throw, _) => ReturnType::Exception, - _ => unreachable!(), - }; + let (return_type, token_pos) = input + .next() + .map(|(token, pos)| { + ( + match token { + Token::Return => ReturnType::Return, + Token::Throw => ReturnType::Exception, + _ => unreachable!(), + }, + pos, + ) + }) + .unwrap(); match input.peek().unwrap() { // `return`/`throw` at (Token::EOF, pos) => Ok(Some(Stmt::ReturnWithVal(Box::new(( (return_type, *pos), None, + token_pos, ))))), // `return;` or `throw;` (Token::SemiColon, _) => Ok(Some(Stmt::ReturnWithVal(Box::new(( (return_type, settings.pos), None, + token_pos, ))))), // `return` or `throw` with expression (_, _) => { @@ -2852,6 +2910,7 @@ fn parse_stmt( Ok(Some(Stmt::ReturnWithVal(Box::new(( (return_type, pos), Some(expr), + token_pos, ))))) } } From 37ea24afe9c368f5c700d0166eba72b263f234bd Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 28 Jul 2020 19:11:37 +0800 Subject: [PATCH 11/64] Improve docs. --- src/fn_native.rs | 6 ++---- src/token.rs | 37 +++++++++++++++++++++++-------------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/fn_native.rs b/src/fn_native.rs index 22919274..5751351c 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -283,10 +283,8 @@ impl CallableFunction { /// Get the access mode. pub fn access(&self) -> FnAccess { match self { - CallableFunction::Pure(_) - | CallableFunction::Method(_) - | CallableFunction::Iterator(_) => FnAccess::Public, - CallableFunction::Script(f) => f.access, + Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => FnAccess::Public, + Self::Script(f) => f.access, } } /// Get a reference to a native Rust function. diff --git a/src/token.rs b/src/token.rs index b8576133..edbac0aa 100644 --- a/src/token.rs +++ b/src/token.rs @@ -30,8 +30,11 @@ pub type TokenStream<'a, 't> = Peekable>; /// A location (line number + character position) in the input script. /// -/// In order to keep footprint small, both line number and character position have 16-bit unsigned resolution, -/// meaning they go up to a maximum of 65,535 lines and characters per line. +/// # Limitations +/// +/// In order to keep footprint small, both line number and character position have 16-bit resolution, +/// meaning they go up to a maximum of 65,535 lines and 65,535 characters per line. +/// /// Advancing beyond the maximum line length or maximum number of lines is not an error but has no effect. #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] pub struct Position { @@ -43,6 +46,13 @@ pub struct Position { impl Position { /// Create a new `Position`. + /// + /// `line` must not be zero. + /// If `position` is zero, then it is at the beginning of a line. + /// + /// # Panics + /// + /// Panics if `line` is zero. pub fn new(line: u16, position: u16) -> Self { assert!(line != 0, "line cannot be zero"); @@ -52,7 +62,7 @@ impl Position { } } - /// Get the line number (1-based), or `None` if no position. + /// Get the line number (1-based), or `None` if there is no position. pub fn line(&self) -> Option { if self.is_none() { None @@ -85,7 +95,6 @@ impl Position { /// # Panics /// /// Panics if already at beginning of a line - cannot rewind to a previous line. - /// pub(crate) fn rewind(&mut self) { assert!(!self.is_none(), "cannot rewind Position::none"); assert!(self.pos > 0, "cannot rewind at position 0"); @@ -104,7 +113,7 @@ impl Position { } /// Create a `Position` representing no position. - pub(crate) fn none() -> Self { + pub fn none() -> Self { Self { line: 0, pos: 0 } } @@ -146,9 +155,9 @@ impl fmt::Debug for Position { pub enum Token { /// An `INT` constant. IntegerConstant(INT), - /// A `FLOAT` constaint. + /// A `FLOAT` constant. /// - /// Never appears under the `no_float` feature. + /// Reserved under the `no_float` feature. #[cfg(not(feature = "no_float"))] FloatConstant(FLOAT), /// An identifier. @@ -249,7 +258,7 @@ pub enum Token { And, /// `fn` /// - /// Never appears under the `no_function` feature. + /// Reserved under the `no_function` feature. #[cfg(not(feature = "no_function"))] Fn, /// `continue` @@ -284,22 +293,22 @@ pub enum Token { PowerOfAssign, /// `private` /// - /// Never appears under the `no_function` feature. + /// Reserved under the `no_function` feature. #[cfg(not(feature = "no_function"))] Private, /// `import` /// - /// Never appears under the `no_module` feature. + /// Reserved under the `no_module` feature. #[cfg(not(feature = "no_module"))] Import, /// `export` /// - /// Never appears under the `no_module` feature. + /// Reserved under the `no_module` feature. #[cfg(not(feature = "no_module"))] Export, /// `as` /// - /// Never appears under the `no_module` feature. + /// Reserved under the `no_module` feature. #[cfg(not(feature = "no_module"))] As, /// A lexer error. @@ -643,7 +652,7 @@ impl Token { } } - /// Is this token a standard keyword? + /// Is this token an active standard keyword? pub fn is_keyword(&self) -> bool { use Token::*; @@ -670,7 +679,7 @@ impl Token { } /// Convert a token into a function name, if possible. - pub fn into_function_name(self) -> Result { + pub(crate) fn into_function_name(self) -> Result { match self { Self::Reserved(s) if is_keyword_function(&s) => Ok(s), Self::Custom(s) | Self::Identifier(s) if is_valid_identifier(s.chars()) => Ok(s), From 411539f3be1db5f8467bd66fbd282af85bdad4df Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 28 Jul 2020 19:11:46 +0800 Subject: [PATCH 12/64] Minimize usage of downcast_ref. --- src/fn_call.rs | 22 +++++++++++----------- src/packages/string_more.rs | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/fn_call.rs b/src/fn_call.rs index 6bd95393..162410f3 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -930,8 +930,8 @@ pub fn run_builtin_binary_op( } if args_type == TypeId::of::() { - let x = *x.downcast_ref::().unwrap(); - let y = *y.downcast_ref::().unwrap(); + let x = x.clone().cast::(); + let y = y.clone().cast::(); #[cfg(not(feature = "unchecked"))] match op { @@ -972,8 +972,8 @@ pub fn run_builtin_binary_op( _ => (), } } else if args_type == TypeId::of::() { - let x = *x.downcast_ref::().unwrap(); - let y = *y.downcast_ref::().unwrap(); + let x = x.clone().cast::(); + let y = y.clone().cast::(); match op { "&" => return Ok(Some((x && y).into())), @@ -998,8 +998,8 @@ pub fn run_builtin_binary_op( _ => (), } } else if args_type == TypeId::of::() { - let x = *x.downcast_ref::().unwrap(); - let y = *y.downcast_ref::().unwrap(); + let x = x.clone().cast::(); + let y = y.clone().cast::(); match op { "==" => return Ok(Some((x == y).into())), @@ -1020,8 +1020,8 @@ pub fn run_builtin_binary_op( #[cfg(not(feature = "no_float"))] if args_type == TypeId::of::() { - let x = *x.downcast_ref::().unwrap(); - let y = *y.downcast_ref::().unwrap(); + let x = x.clone().cast::(); + let y = y.clone().cast::(); match op { "+" => return Ok(Some((x + y).into())), @@ -1059,7 +1059,7 @@ pub fn run_builtin_op_assignment( if args_type == TypeId::of::() { let x = x.downcast_mut::().unwrap(); - let y = *y.downcast_ref::().unwrap(); + let y = y.clone().cast::(); #[cfg(not(feature = "unchecked"))] match op { @@ -1095,7 +1095,7 @@ pub fn run_builtin_op_assignment( } } else if args_type == TypeId::of::() { let x = x.downcast_mut::().unwrap(); - let y = *y.downcast_ref::().unwrap(); + let y = y.clone().cast::(); match op { "&=" => return Ok(Some(*x = *x && y)), @@ -1115,7 +1115,7 @@ pub fn run_builtin_op_assignment( #[cfg(not(feature = "no_float"))] if args_type == TypeId::of::() { let x = x.downcast_mut::().unwrap(); - let y = *y.downcast_ref::().unwrap(); + let y = y.clone().cast::(); match op { "+=" => return Ok(Some(*x += y)), diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 62a5fdb2..6ddc2b0c 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -15,7 +15,7 @@ use crate::stdlib::{ any::TypeId, boxed::Box, fmt::Display, - format, + format, mem, string::{String, ToString}, vec::Vec, }; @@ -242,7 +242,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str } if len > 0 { - let ch = *args[2].downcast_ref::< char>().unwrap(); + let ch = mem::take(args[2]).cast::(); let s = args[0].downcast_mut::().unwrap(); let orig_len = s.chars().count(); From e2daba55d7a170980013cc370d5525a20312f8f5 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 29 Jul 2020 13:57:17 +0800 Subject: [PATCH 13/64] Refine unicode-xid-ident feature writeup. --- Cargo.toml | 4 ++-- RELEASES.md | 2 +- doc/src/language/variables.md | 5 +++++ doc/src/start/features.md | 32 ++++++++++++++++---------------- src/token.rs | 13 +++++++------ tests/tokens.rs | 12 ++++++------ 6 files changed, 37 insertions(+), 31 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 66bdba28..f049777e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ include = [ "Cargo.toml" ] keywords = [ "scripting" ] -categories = [ "no-std", "embedded", "parser-implementations" ] +categories = [ "no-std", "embedded", "wasm", "parser-implementations" ] [dependencies] num-traits = { version = "0.2.11", default-features = false } @@ -34,7 +34,7 @@ no_object = [] # no custom objects no_function = [] # no script-defined functions no_module = [] # no modules internals = [] # expose internal data structures -unicode-xid-ident = ["unicode-xid"] # allow unicode-xid for identifiers. +unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. # compiling for no-std no_std = [ "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ] diff --git a/RELEASES.md b/RELEASES.md index 87d12d44..7f59cddf 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -20,7 +20,7 @@ New features * Custom syntax now works even without the `internals` feature. * Currying of function pointers is supported via the new `curry` keyword. * `Module::set_indexer_get_set_fn` is added as a shorthand of both `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn`. -* New `unicode-xid-ident` feature to allow unicode-xid for identifiers. +* New `unicode-xid-ident` feature to allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) for identifiers. Breaking changes ---------------- diff --git a/doc/src/language/variables.md b/doc/src/language/variables.md index be4abbce..455c075b 100644 --- a/doc/src/language/variables.md +++ b/doc/src/language/variables.md @@ -21,6 +21,11 @@ Variable names are case _sensitive_. Variable names also cannot be the same as a [keyword]. +### Unicode Standard Annex #31 Identifiers + +The [`unicode-xid-ident`] feature expands the allowed characters for variable names to the set defined by +[Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/). + Declare a Variable ------------------ diff --git a/doc/src/start/features.md b/doc/src/start/features.md index 83311cf1..0f6b0f53 100644 --- a/doc/src/start/features.md +++ b/doc/src/start/features.md @@ -11,22 +11,22 @@ Notice that this deviates from Rust norm where features are _additive_. Excluding unneeded functionalities can result in smaller, faster builds as well as more control over what a script can (or cannot) do. -| Feature | Description | -| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `unchecked` | Disable arithmetic checking (such as over-flows and division by zero), call stack depth limit, operations count limit and modules loading limit.
Beware that a bad script may panic the entire system! | -| `sync` | Restrict 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` | Disable [script optimization]. | -| `no_float` | Disable floating-point numbers and math. | -| `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | -| `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | -| `no_index` | Disable [arrays] and indexing features. | -| `no_object` | Disable support for [custom types] and [object maps]. | -| `no_function` | Disable script-defined [functions]. | -| `no_module` | Disable loading external [modules]. | -| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | -| `serde` | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. | -| `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. | -| `unicode-xid-ident` | Allow unicode-xid for identifiers. | +| Feature | Description | +| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `unchecked` | Disable arithmetic checking (such as over-flows and division by zero), call stack depth limit, operations count limit and modules loading limit.
Beware that a bad script may panic the entire system! | +| `sync` | Restrict 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` | Disable [script optimization]. | +| `no_float` | Disable floating-point numbers and math. | +| `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | +| `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | +| `no_index` | Disable [arrays] and indexing features. | +| `no_object` | Disable support for [custom types] and [object maps]. | +| `no_function` | Disable script-defined [functions]. | +| `no_module` | Disable loading external [modules]. | +| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | +| `serde` | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. | +| `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. | +| `unicode-xid-ident` | Allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) as identifiers. | Example diff --git a/src/token.rs b/src/token.rs index 9f5456d9..77187304 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1104,7 +1104,7 @@ fn get_next_token_inner( |err| (Token::LexError(Box::new(err.0)), err.1), |result| { let mut chars = result.chars(); - let first = chars.next(); + let first = chars.next().unwrap(); if chars.next().is_some() { ( @@ -1112,10 +1112,7 @@ fn get_next_token_inner( start_pos, ) } else { - ( - Token::CharConstant(first.expect("should be Some")), - start_pos, - ) + (Token::CharConstant(first), start_pos) } }, )) @@ -1419,6 +1416,7 @@ fn get_identifier( } /// Is this keyword allowed as a function? +#[inline(always)] pub fn is_keyword_function(name: &str) -> bool { name == KEYWORD_PRINT || name == KEYWORD_DEBUG @@ -1446,22 +1444,25 @@ pub fn is_valid_identifier(name: impl Iterator) -> bool { } #[cfg(feature = "unicode-xid-ident")] +#[inline(always)] fn is_id_first_alphabetic(x: char) -> bool { unicode_xid::UnicodeXID::is_xid_start(x) } #[cfg(feature = "unicode-xid-ident")] +#[inline(always)] fn is_id_continue(x: char) -> bool { unicode_xid::UnicodeXID::is_xid_continue(x) } #[cfg(not(feature = "unicode-xid-ident"))] - +#[inline(always)] fn is_id_first_alphabetic(x: char) -> bool { x.is_ascii_alphabetic() } #[cfg(not(feature = "unicode-xid-ident"))] +#[inline(always)] fn is_id_continue(x: char) -> bool { x.is_ascii_alphanumeric() || x == '_' } diff --git a/tests/tokens.rs b/tests/tokens.rs index 843fc719..e393663d 100644 --- a/tests/tokens.rs +++ b/tests/tokens.rs @@ -57,9 +57,9 @@ fn test_tokens_unicode_xid_ident() -> Result<(), Box> { let engine = Engine::new(); let result = engine.eval::( r" - fn すべての答え() { 42 } - すべての答え() - ", + fn すべての答え() { 42 } + すべての答え() + ", ); #[cfg(feature = "unicode-xid-ident")] assert_eq!(result?, 42); @@ -69,9 +69,9 @@ fn test_tokens_unicode_xid_ident() -> Result<(), Box> { let result = engine.eval::( r" - fn _1() { 1 } - _1() - ", + fn _1() { 1 } + _1() + ", ); assert!(result.is_err()); From 40850e6b1e6f130622957378c48e317dd93e8000 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 29 Jul 2020 16:09:48 +0800 Subject: [PATCH 14/64] Fix benchmarks. --- benches/eval_expression.rs | 8 ++++---- benches/parsing.rs | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/benches/eval_expression.rs b/benches/eval_expression.rs index ceaaaa17..82fd1b78 100644 --- a/benches/eval_expression.rs +++ b/benches/eval_expression.rs @@ -48,7 +48,7 @@ fn bench_eval_expression_optimized_simple(bench: &mut Bencher) { 2 > 1 && "something" != "nothing" || "2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" && - [array, with, spaces].len <= #{prop:name}.len && + [array, has, spaces].len <= #{prop:name}.len && modifierTest + 1000 / 2 > (80 * 100 % 2) "#; @@ -65,7 +65,7 @@ fn bench_eval_expression_optimized_full(bench: &mut Bencher) { 2 > 1 && "something" != "nothing" || "2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" && - [array, with, spaces].len <= #{prop:name}.len && + [array, has, spaces].len <= #{prop:name}.len && modifierTest + 1000 / 2 > (80 * 100 % 2) "#; @@ -82,7 +82,7 @@ fn bench_eval_call_expression(bench: &mut Bencher) { 2 > 1 && "something" != "nothing" || "2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" && - [array, with, spaces].len <= #{prop:name}.len && + [array, has, spaces].len <= #{prop:name}.len && modifierTest + 1000 / 2 > (80 * 100 % 2) "#; @@ -97,7 +97,7 @@ fn bench_eval_call(bench: &mut Bencher) { 2 > 1 && "something" != "nothing" || "2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" && - [array, with, spaces].len <= #{prop:name}.len && + [array, has, spaces].len <= #{prop:name}.len && modifierTest + 1000 / 2 > (80 * 100 % 2) "#; diff --git a/benches/parsing.rs b/benches/parsing.rs index 8e84bd8d..6bde9dd1 100644 --- a/benches/parsing.rs +++ b/benches/parsing.rs @@ -32,7 +32,7 @@ fn bench_parse_full(bench: &mut Bencher) { 2 > 1 && "something" != "nothing" || "2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" && - [array, with, spaces].len <= #{prop:name}.len && + [array, has, spaces].len <= #{prop:name}.len && modifierTest + 1000 / 2 > (80 * 100 % 2) "#; @@ -109,7 +109,7 @@ fn bench_parse_optimize_simple(bench: &mut Bencher) { 2 > 1 && "something" != "nothing" || "2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" && - [array, with, spaces].len <= #{prop:name}.len && + [array, has, spaces].len <= #{prop:name}.len && modifierTest + 1000 / 2 > (80 * 100 % 2) "#; @@ -125,7 +125,7 @@ fn bench_parse_optimize_full(bench: &mut Bencher) { 2 > 1 && "something" != "nothing" || "2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" && - [array, with, spaces].len <= #{prop:name}.len && + [array, has, spaces].len <= #{prop:name}.len && modifierTest + 1000 / 2 > (80 * 100 % 2) "#; From afbcd0fc0b56cc6820e857017eeb86734f79aae6 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 29 Jul 2020 16:10:06 +0800 Subject: [PATCH 15/64] Replace StaticVec with SmallVec. --- Cargo.toml | 1 + doc/src/about/features.md | 2 +- src/engine.rs | 8 +- src/optimize.rs | 3 +- src/parser.rs | 24 +- src/token.rs | 48 ++-- src/utils.rs | 555 +------------------------------------- 7 files changed, 54 insertions(+), 587 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f049777e..6b2a2e55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ categories = [ "no-std", "embedded", "wasm", "parser-implementations" ] [dependencies] num-traits = { version = "0.2.11", default-features = false } +smallvec = { version = "1.4.1", default-features = false } [features] #default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"] diff --git a/doc/src/about/features.md b/doc/src/about/features.md index ffa28cdc..245668b2 100644 --- a/doc/src/about/features.md +++ b/doc/src/about/features.md @@ -14,7 +14,7 @@ Easy * Easily [call a script-defined function]({{rootUrl}}/engine/call-fn.md) from Rust. -* Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/) to do checked arithmetic operations); +* Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/) to do checked arithmetic operations, and [`smallvec`](https://crates.io/crates/smallvec/)); for [`no-std`] builds, a number of additional dependencies are pulled in to provide for functionalities that used to be in `std`. Fast diff --git a/src/engine.rs b/src/engine.rs index e8cf5c3c..7806382f 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -445,7 +445,7 @@ pub fn search_imports<'s>( state: &mut State, modules: &Box, ) -> Result<&'s Module, Box> { - let (root, root_pos) = modules.get(0); + let (root, root_pos) = &modules[0]; // Qualified - check if the root module is directly indexed let index = if state.always_search { @@ -478,7 +478,7 @@ pub fn search_imports_mut<'s>( state: &mut State, modules: &Box, ) -> Result<&'s mut Module, Box> { - let (root, root_pos) = modules.get(0); + let (root, root_pos) = &modules[0]; // Qualified - check if the root module is directly indexed let index = if state.always_search { @@ -652,7 +652,7 @@ impl Engine { }; // Pop the last index value - let idx_val = idx_values.pop(); + let idx_val = idx_values.pop().unwrap(); match chain_type { #[cfg(not(feature = "no_index"))] @@ -1007,7 +1007,7 @@ impl Engine { idx_values.push(Dynamic::from(arg_values)); } Expr::FnCall(_) => unreachable!(), - Expr::Property(_) => idx_values.push(()), // Store a placeholder - no need to copy the property name + Expr::Property(_) => idx_values.push(().into()), // Store a placeholder - no need to copy the property name Expr::Index(x) | Expr::Dot(x) => { let (lhs, rhs, _) = x.as_ref(); diff --git a/src/optimize.rs b/src/optimize.rs index 27f6f816..c047cd1b 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -20,6 +20,7 @@ use crate::parser::CustomExpr; use crate::stdlib::{ boxed::Box, iter::empty, + mem, string::{String, ToString}, vec, vec::Vec, @@ -436,7 +437,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // Array literal where everything is pure - promote the indexed item. // All other items can be thrown away. state.set_dirty(); - let mut expr = a.0.take(i.0 as usize); + let mut expr = a.0.remove(i.0 as usize); expr.set_position(a.1); expr } diff --git a/src/parser.rs b/src/parser.rs index 417c35fb..03bb2a96 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1103,7 +1103,7 @@ fn parse_fn_call( eat_token(input, Token::RightParen); let hash_script = if let Some(modules) = modules.as_mut() { - modules.set_index(state.find_module(&modules.get(0).0)); + modules.set_index(state.find_module(&modules[0].0)); // Rust functions are indexed in two steps: // 1) Calculate a hash in a similar manner to script-defined functions, @@ -1145,7 +1145,7 @@ fn parse_fn_call( eat_token(input, Token::RightParen); let hash_script = if let Some(modules) = modules.as_mut() { - modules.set_index(state.find_module(&modules.get(0).0)); + modules.set_index(state.find_module(&modules[0].0)); // Rust functions are indexed in two steps: // 1) Calculate a hash in a similar manner to script-defined functions, @@ -1678,7 +1678,7 @@ fn parse_primary( // Qualifiers + variable name *hash = calc_fn_hash(modules.iter().map(|(v, _)| v.as_str()), name, 0, empty()); - modules.set_index(state.find_module(&modules.get(0).0)); + modules.set_index(state.find_module(&modules[0].0)); } _ => (), } @@ -1936,7 +1936,7 @@ fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result { - return Err(PERR::PropertyExpected.into_err(x.1.unwrap().get(0).1)); + return Err(PERR::PropertyExpected.into_err(x.1.unwrap()[0].1)); } // lhs.prop (lhs, prop @ Expr::Property(_)) => Expr::Dot(Box::new((lhs, prop, op_pos))), @@ -2197,25 +2197,25 @@ fn parse_binary_op( | Token::GreaterThanEqualsTo => Expr::FnCall(Box::new((op, None, hash, args, cmp_def))), Token::Or => { - let rhs = args.pop(); - let current_lhs = args.pop(); + let rhs = args.pop().unwrap(); + let current_lhs = args.pop().unwrap(); Expr::Or(Box::new((current_lhs, rhs, pos))) } Token::And => { - let rhs = args.pop(); - let current_lhs = args.pop(); + let rhs = args.pop().unwrap(); + let current_lhs = args.pop().unwrap(); Expr::And(Box::new((current_lhs, rhs, pos))) } Token::In => { - let rhs = args.pop(); - let current_lhs = args.pop(); + let rhs = args.pop().unwrap(); + let current_lhs = args.pop().unwrap(); make_in_expr(current_lhs, rhs, pos)? } #[cfg(not(feature = "no_object"))] Token::Period => { - let rhs = args.pop(); - let current_lhs = args.pop(); + let rhs = args.pop().unwrap(); + let current_lhs = args.pop().unwrap(); make_dot_expr(current_lhs, rhs, pos)? } diff --git a/src/token.rs b/src/token.rs index 77187304..7d1a07ba 100644 --- a/src/token.rs +++ b/src/token.rs @@ -21,7 +21,6 @@ use crate::stdlib::{ iter::Peekable, str::{Chars, FromStr}, string::{String, ToString}, - vec::Vec, }; type LERR = LexError; @@ -747,8 +746,8 @@ pub fn parse_string_literal( pos: &mut Position, enclosing_char: char, ) -> Result { - let mut result = Vec::new(); - let mut escape = String::with_capacity(12); + let mut result: StaticVec = Default::default(); + let mut escape: StaticVec = Default::default(); loop { let next_char = stream.get_next().ok_or((LERR::UnterminatedString, *pos))?; @@ -787,8 +786,8 @@ pub fn parse_string_literal( // \x??, \u????, \U???????? ch @ 'x' | ch @ 'u' | ch @ 'U' if !escape.is_empty() => { let mut seq = escape.clone(); - seq.push(ch); escape.clear(); + seq.push(ch); let mut out_val: u32 = 0; let len = match ch { @@ -799,23 +798,31 @@ pub fn parse_string_literal( }; for _ in 0..len { - let c = stream - .get_next() - .ok_or_else(|| (LERR::MalformedEscapeSequence(seq.to_string()), *pos))?; + let c = stream.get_next().ok_or_else(|| { + ( + LERR::MalformedEscapeSequence(seq.iter().cloned().collect()), + *pos, + ) + })?; seq.push(c); pos.advance(); out_val *= 16; - out_val += c - .to_digit(16) - .ok_or_else(|| (LERR::MalformedEscapeSequence(seq.to_string()), *pos))?; + out_val += c.to_digit(16).ok_or_else(|| { + ( + LERR::MalformedEscapeSequence(seq.iter().cloned().collect()), + *pos, + ) + })?; } - result.push( - char::from_u32(out_val) - .ok_or_else(|| (LERR::MalformedEscapeSequence(seq), *pos))?, - ); + result.push(char::from_u32(out_val).ok_or_else(|| { + ( + LERR::MalformedEscapeSequence(seq.into_iter().collect()), + *pos, + ) + })?); } // \{enclosing_char} - escaped @@ -828,7 +835,12 @@ pub fn parse_string_literal( ch if enclosing_char == ch && escape.is_empty() => break, // Unknown escape sequence - _ if !escape.is_empty() => return Err((LERR::MalformedEscapeSequence(escape), *pos)), + _ if !escape.is_empty() => { + return Err(( + LERR::MalformedEscapeSequence(escape.into_iter().collect()), + *pos, + )) + } // Cannot have new-lines inside string literals '\n' => { @@ -983,7 +995,7 @@ fn get_next_token_inner( // digit ... ('0'..='9', _) => { - let mut result = Vec::new(); + let mut result: StaticVec = Default::default(); let mut radix_base: Option = None; result.push(c); @@ -1385,7 +1397,7 @@ fn get_identifier( start_pos: Position, first_char: char, ) -> Option<(Token, Position)> { - let mut result = Vec::new(); + let mut result: StaticVec<_> = Default::default(); result.push(first_char); while let Some(next_char) = stream.peek_next() { @@ -1400,7 +1412,7 @@ fn get_identifier( let is_valid_identifier = is_valid_identifier(result.iter().cloned()); - let identifier: String = result.into_iter().collect(); + let identifier = result.into_iter().collect(); if !is_valid_identifier { return Some(( diff --git a/src/utils.rs b/src/utils.rs index 760c809e..a6cfeb26 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,8 +1,4 @@ //! Module containing various utility types and functions. -//! -//! # Safety -//! -//! The `StaticVec` type has some `unsafe` blocks to handle conversions between `MaybeUninit` and regular types. use crate::fn_native::{shared_make_mut, shared_take, Shared}; @@ -13,12 +9,9 @@ use crate::stdlib::{ fmt, hash::{BuildHasher, Hash, Hasher}, iter::FromIterator, - mem, - mem::MaybeUninit, - ops::{Add, AddAssign, Deref, DerefMut, Drop, Index, IndexMut}, + ops::{Add, AddAssign, Deref}, str::FromStr, string::{String, ToString}, - vec::Vec, }; #[cfg(not(feature = "no_std"))] @@ -27,6 +20,8 @@ use crate::stdlib::collections::hash_map::DefaultHasher; #[cfg(feature = "no_std")] use ahash::AHasher; +use smallvec::SmallVec; + /// A hasher that only takes one single `u64` and returns it as a hash key. /// /// # Panics @@ -92,549 +87,7 @@ pub fn calc_fn_spec<'a>( s.finish() } -/// [INTERNALS] An array-like type that holds a number of values in static storage for no-allocation, quick access. -/// Exported under the `internals` feature only. -/// -/// 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. -/// -/// ## WARNING -/// -/// This type is volatile and may change. -// -// TODO - remove unsafe code -pub struct StaticVec { - /// Total number of values held. - len: usize, - /// 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(); - } -} - -impl Hash for StaticVec { - fn hash(&self, state: &mut H) { - self.iter().for_each(|x| x.hash(state)); - } -} - -impl Default for StaticVec { - fn default() -> Self { - Self { - len: 0, - list: unsafe { mem::MaybeUninit::uninit().assume_init() }, - more: Vec::new(), - } - } -} - -impl PartialEq for StaticVec { - fn eq(&self, other: &Self) -> bool { - 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) - } - } -} - -impl Clone for StaticVec { - fn clone(&self) -> Self { - let mut value: Self = Default::default(); - value.len = self.len; - - if self.is_fixed_storage() { - for x in 0..self.len { - let item = self.list.get(x).unwrap(); - let item_value = unsafe { mem::transmute::<_, &T>(item) }; - value.list[x] = MaybeUninit::new(item_value.clone()); - } - } else { - value.more = self.more.clone(); - } - - value - } -} - -impl Eq for StaticVec {} - -impl FromIterator for StaticVec { - fn from_iter>(iter: X) -> Self { - let mut vec = StaticVec::new(); - - for x in iter { - vec.push(x); - } - - vec - } -} - -impl IntoIterator for StaticVec { - type Item = T; - type IntoIter = Box>; - - fn into_iter(self) -> Self::IntoIter { - self.into_iter() - } -} - -impl StaticVec { - /// Create a new `StaticVec`. - pub fn new() -> Self { - Default::default() - } - /// Empty the `StaticVec`. - pub fn clear(&mut self) { - if self.is_fixed_storage() { - for x in 0..self.len { - 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 == MAX_STATIC_VEC { - self.move_fixed_into_vec(MAX_STATIC_VEC); - self.more.push(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 == MAX_STATIC_VEC { - self.move_fixed_into_vec(MAX_STATIC_VEC); - self.more.insert(index, value.into()); - } else if self.is_fixed_storage() { - // Move all items one slot to the right - for x in (index..self.len).rev() { - let orig_value = self.extract_from_list(x); - self.set_into_list(x + 1, orig_value, false); - } - self.set_into_list(index, value.into(), false); - } else { - self.more.insert(index, value.into()); - } - self.len += 1; - } - /// Pop a value from the end of this `StaticVec`. - /// - /// # Panics - /// - /// Panics if the `StaticVec` is empty. - pub fn pop(&mut self) -> T { - if self.is_empty() { - panic!("nothing to pop!"); - } - - if self.is_fixed_storage() { - let value = self.extract_from_list(self.len - 1); - self.len -= 1; - value - } else { - let value = self.more.pop().unwrap(); - self.len -= 1; - - // 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 - } - } - /// 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"); - } - - if self.is_fixed_storage() { - let value = self.extract_from_list(index); - - // Move all items one slot to the left - for x in index + 1..self.len { - let orig_value = self.extract_from_list(x); - self.set_into_list(x - 1, orig_value, false); - } - self.len -= 1; - - value - } else { - let value = self.more.remove(index); - self.len -= 1; - - // 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 - } - } - /// Get the number of items in this `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 - /// - /// 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 = unsafe { mem::transmute::<_, &[T; MAX_STATIC_VEC]>(&self.list) }; - - if self.is_fixed_storage() { - list.get(index).unwrap() - } else { - self.more.get(index).unwrap() - } - } - /// Get a mutable reference to the item at a particular index. - /// - /// # Panics - /// - /// 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"); - } - - let list = unsafe { mem::transmute::<_, &mut [T; MAX_STATIC_VEC]>(&mut self.list) }; - - if self.is_fixed_storage() { - 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 { - let list = unsafe { mem::transmute::<_, &[T; MAX_STATIC_VEC]>(&self.list) }; - - if self.is_fixed_storage() { - list[..self.len].iter() - } else { - self.more.iter() - } - } - /// Get a mutable iterator to entries in the `StaticVec`. - pub fn iter_mut(&mut self) -> impl Iterator { - let list = unsafe { mem::transmute::<_, &mut [T; MAX_STATIC_VEC]>(&mut self.list) }; - - if self.is_fixed_storage() { - list[..self.len].iter_mut() - } else { - self.more.iter_mut() - } - } -} - -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 `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.is_fixed_storage() { - unsafe { mem::transmute(self.list.get_mut(index).unwrap()) } - } else { - self.more.get_mut(index).unwrap() - }) - } -} - -impl fmt::Debug for StaticVec { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&self.iter().collect::>(), f) - } -} - -impl AsRef<[T]> for StaticVec { - fn as_ref(&self) -> &[T] { - let list = unsafe { mem::transmute::<_, &[T; MAX_STATIC_VEC]>(&self.list) }; - - if self.is_fixed_storage() { - &list[..self.len] - } else { - &self.more[..] - } - } -} - -impl AsMut<[T]> for StaticVec { - fn as_mut(&mut self) -> &mut [T] { - let list = unsafe { mem::transmute::<_, &mut [T; MAX_STATIC_VEC]>(&mut self.list) }; - - if self.is_fixed_storage() { - &mut list[..self.len] - } else { - &mut self.more[..] - } - } -} - -impl Deref for StaticVec { - type Target = [T]; - fn deref(&self) -> &Self::Target { - self.as_ref() - } -} - -impl DerefMut for StaticVec { - fn deref_mut(&mut self) -> &mut Self::Target { - self.as_mut() - } -} - -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 <= MAX_STATIC_VEC { - value.move_fixed_into_vec(value.len); - } - value.len = 0; - - 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 <= MAX_STATIC_VEC { - for x in (0..arr.len).rev() { - arr.set_into_list(x, value.pop().unwrap(), false); - } - } else { - arr.more = value; - } - - arr - } -} +pub type StaticVec = SmallVec<[T; 4]>; /// The system immutable string type. /// From 8e51988b660437dc8396e753365902d425df5c15 Mon Sep 17 00:00:00 2001 From: Ilya Lakhin Date: Sun, 26 Jul 2020 09:18:18 +0700 Subject: [PATCH 16/64] A prototype of a new feature to automatically curry of external scope variables --- Cargo.toml | 1 + src/parser.rs | 135 ++++++++++++++++++++++++++++++++++++++--------- tests/call_fn.rs | 21 ++++++++ 3 files changed, 132 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f049777e..606412c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ only_i64 = [] # set INT=i64 (default) and disable support for all other in no_index = [] # no arrays and indexing no_object = [] # no custom objects no_function = [] # no script-defined functions +no_closures = [] # no automatic read/write binding of anonymous function's local variables to it's external context no_module = [] # no modules internals = [] # expose internal data structures unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. diff --git a/src/parser.rs b/src/parser.rs index 417c35fb..f8881342 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,7 +2,10 @@ use crate::any::{Dynamic, Union}; use crate::calc_fn_hash; -use crate::engine::{Engine, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; +use crate::engine::{ + Engine, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT, + KEYWORD_FN_PTR_CURRY, +}; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::fn_native::Shared; use crate::module::{Module, ModuleRef}; @@ -399,12 +402,14 @@ pub enum ReturnType { Exception, } -#[derive(Clone)] -struct ParseState<'e> { +struct ParseState<'e, 's> { /// Reference to the scripting `Engine`. engine: &'e Engine, /// Encapsulates a local stack with variable names to simulate an actual runtime scope. stack: Vec<(String, ScopeEntryType)>, + /// Tracks a list of external variables(variables that are not explicitly + /// declared in the scope during AST evaluation). + externals: &'s mut Vec, /// Encapsulates a local stack with variable names to simulate an actual runtime scope. modules: Vec, /// Maximum levels of expression nesting. @@ -415,40 +420,103 @@ struct ParseState<'e> { max_function_expr_depth: usize, } -impl<'e> ParseState<'e> { +impl<'e, 's> ParseState<'e, 's> { /// Create a new `ParseState`. - pub fn new( + fn new( engine: &'e Engine, + externals: &'s mut Vec, #[cfg(not(feature = "unchecked"))] max_expr_depth: usize, #[cfg(not(feature = "unchecked"))] max_function_expr_depth: usize, ) -> Self { Self { engine, + #[cfg(not(feature = "unchecked"))] max_expr_depth, + #[cfg(not(feature = "unchecked"))] max_function_expr_depth, + externals, stack: Default::default(), modules: Default::default(), - #[cfg(not(feature = "unchecked"))] - max_expr_depth, - #[cfg(not(feature = "unchecked"))] - max_function_expr_depth, } } - /// Find a variable by name in the `ParseState`, searching in reverse. + + /// Creates a new `ParseState` with empty `stack` and `modules` lists, but + /// deriving other settings from the passed `ParseState` instance. + fn derive(&'s mut self) -> Self { + Self { + engine: self.engine, + #[cfg(not(feature = "unchecked"))] + max_expr_depth: self.max_expr_depth, + #[cfg(not(feature = "unchecked"))] + max_function_expr_depth: self.max_function_expr_depth, + externals: self.externals, + stack: Default::default(), + modules: Default::default(), + } + } + + /// Find explicitly declared variable by name in the `ParseState`, + /// searching in reverse order. + /// + /// If the variable is not present in the scope adds it to the list of + /// external variables + /// /// The return value is the offset to be deducted from `Stack::len`, /// i.e. the top element of the `ParseState` is offset 1. - /// Return zero when the variable name is not found in the `ParseState`. - pub fn find_var(&self, name: &str) -> Option { - self.stack + /// Return `None` when the variable name is not found in the `stack`. + fn access_var(&mut self, name: &str) -> Option { + let mut index = self.stack .iter() .rev() .enumerate() .find(|(_, (n, _))| *n == name) - .and_then(|(i, _)| NonZeroUsize::new(i + 1)) + .and_then(|(i, _)| NonZeroUsize::new(i + 1)); + + if index.is_some() { + return index + } + + #[cfg(not(feature = "no_closures"))] + if self.externals.iter().find(|n| *n == name).is_none() { + self.externals.push(name.to_string()); + } + + None } + + /// Creates a curry expression from a list of external variables + fn make_curry_from_externals(&self, fn_expr: Expr, settings: &ParseSettings) -> Expr { + if self.externals.is_empty() { + return fn_expr + } + + let mut args = StaticVec::new(); + + for var in self.externals.iter() { + args.push(Expr::Variable(Box::new(( + (var.clone(), settings.pos), + None, + 0, + None, + )))); + } + + Expr::Dot(Box::new(( + fn_expr, + Expr::FnCall(Box::new(( + (KEYWORD_FN_PTR_CURRY.into(), false, settings.pos), + None, + calc_fn_hash(empty(), KEYWORD_FN_PTR_CURRY, self.externals.len(), empty()), + args, + None, + ))), + settings.pos, + ))) + } + /// Find a module by name in the `ParseState`, searching in reverse. /// The return value is the offset to be deducted from `Stack::len`, /// i.e. the top element of the `ParseState` is offset 1. - /// Return zero when the variable name is not found in the `ParseState`. - pub fn find_module(&self, name: &str) -> Option { + /// Return `None` when the variable name is not found in the `ParseState`. + fn find_module(&self, name: &str) -> Option { self.modules .iter() .rev() @@ -1577,7 +1645,7 @@ fn parse_primary( Token::CharConstant(c) => Expr::CharConstant(Box::new((c, settings.pos))), Token::StringConstant(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))), Token::Identifier(s) => { - let index = state.find_var(&s); + let index = state.access_var(&s); Expr::Variable(Box::new(((s, settings.pos), None, 0, index))) } // Function call is allowed to have reserved keyword @@ -1778,9 +1846,10 @@ fn parse_unary( // | ... #[cfg(not(feature = "no_function"))] Token::Pipe | Token::Or => { + let mut _externals = Default::default(); let mut state = ParseState::new( state.engine, - #[cfg(not(feature = "unchecked"))] + &mut _externals, state.max_function_expr_depth, #[cfg(not(feature = "unchecked"))] state.max_function_expr_depth, @@ -2822,8 +2891,10 @@ fn parse_stmt( match input.next().unwrap() { (Token::Fn, pos) => { + let mut _externals = Default::default(); let mut state = ParseState::new( state.engine, + &mut _externals, #[cfg(not(feature = "unchecked"))] state.max_function_expr_depth, #[cfg(not(feature = "unchecked"))] @@ -3091,7 +3162,16 @@ fn parse_anon_fn( let body = parse_stmt(input, state, lib, settings.level_up()) .map(|stmt| stmt.unwrap_or_else(|| Stmt::Noop(pos)))?; - let params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect(); + let mut static_params = StaticVec::::new(); + + #[cfg(not(feature = "no_closures"))] + for closure in state.externals.iter() { + static_params.push(closure.clone()); + } + + for param in params.into_iter() { + static_params.push(param.0); + } // Calculate hash #[cfg(feature = "no_std")] @@ -3099,8 +3179,8 @@ fn parse_anon_fn( #[cfg(not(feature = "no_std"))] let mut s = DefaultHasher::new(); - s.write_usize(params.len()); - params.iter().for_each(|a| a.hash(&mut s)); + s.write_usize(static_params.len()); + static_params.iter().for_each(|a| a.hash(&mut s)); body.hash(&mut s); let hash = s.finish(); @@ -3110,12 +3190,15 @@ fn parse_anon_fn( let script = ScriptFnDef { name: fn_name.clone(), access: FnAccess::Public, - params, + params: static_params, body, pos: settings.pos, }; - let expr = Expr::FnPointer(Box::new((fn_name, settings.pos))); + let mut expr = state.make_curry_from_externals( + Expr::FnPointer(Box::new((fn_name, settings.pos))), + &settings, + ); Ok((expr, script)) } @@ -3128,9 +3211,10 @@ impl Engine { optimization_level: OptimizationLevel, ) -> Result { let mut functions = Default::default(); - + let mut _externals = Default::default(); let mut state = ParseState::new( self, + &mut _externals, #[cfg(not(feature = "unchecked"))] self.limits.max_expr_depth, #[cfg(not(feature = "unchecked"))] @@ -3174,9 +3258,10 @@ impl Engine { ) -> Result<(Vec, Vec), ParseError> { let mut statements: Vec = Default::default(); let mut functions = Default::default(); - + let mut _externals = Default::default(); let mut state = ParseState::new( self, + &mut _externals, #[cfg(not(feature = "unchecked"))] self.limits.max_expr_depth, #[cfg(not(feature = "unchecked"))] diff --git a/tests/call_fn.rs b/tests/call_fn.rs index e75d93ec..e2b64dfe 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -209,3 +209,24 @@ fn test_fn_ptr_curry_call() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_fn_closures() -> Result<(), Box> { + let mut engine = Engine::new(); + + let res = engine.eval::( + r#" + let x = 100; + + let f = || x; + + let x = 200; + + f.call() + "# + ).unwrap(); + + panic!("{:#?}", res); + + Ok(()) +} From 48356abc83e7936ff1dea6f9ed8ec9c6692c159c Mon Sep 17 00:00:00 2001 From: Ilya Lakhin Date: Wed, 29 Jul 2020 18:52:54 +0700 Subject: [PATCH 17/64] Autocurry fixes and test --- src/parser.rs | 78 ++++++++++++++++++++---------------------------- tests/call_fn.rs | 26 +++++++++------- 2 files changed, 48 insertions(+), 56 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index f8881342..0ab09875 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,10 +2,7 @@ use crate::any::{Dynamic, Union}; use crate::calc_fn_hash; -use crate::engine::{ - Engine, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT, - KEYWORD_FN_PTR_CURRY, -}; +use crate::engine::{Engine, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::fn_native::Shared; use crate::module::{Module, ModuleRef}; @@ -18,6 +15,9 @@ use crate::utils::{StaticVec, StraightHasherBuilder}; #[cfg(not(feature = "no_function"))] use crate::engine::FN_ANONYMOUS; +#[cfg(not(feature = "no_closures"))] +use crate::engine::KEYWORD_FN_PTR_CURRY; + #[cfg(not(feature = "no_object"))] use crate::engine::{make_getter, make_setter}; @@ -402,29 +402,29 @@ pub enum ReturnType { Exception, } -struct ParseState<'e, 's> { - /// Reference to the scripting `Engine`. +struct ParseState<'e> { + // Reference to the scripting `Engine`. engine: &'e Engine, - /// Encapsulates a local stack with variable names to simulate an actual runtime scope. + // Encapsulates a local stack with variable names to simulate an actual runtime scope. stack: Vec<(String, ScopeEntryType)>, - /// Tracks a list of external variables(variables that are not explicitly - /// declared in the scope during AST evaluation). - externals: &'s mut Vec, - /// Encapsulates a local stack with variable names to simulate an actual runtime scope. + // Tracks a list of external variables(variables that are not explicitly + // declared in the scope during AST evaluation). + #[cfg(not(feature = "no_closures"))] + externals: Vec, + // Encapsulates a local stack with variable names to simulate an actual runtime scope. modules: Vec, - /// Maximum levels of expression nesting. + // Maximum levels of expression nesting. #[cfg(not(feature = "unchecked"))] max_expr_depth: usize, - /// Maximum levels of expression nesting in functions. + // Maximum levels of expression nesting in functions. #[cfg(not(feature = "unchecked"))] max_function_expr_depth: usize, } -impl<'e, 's> ParseState<'e, 's> { +impl<'e> ParseState<'e> { /// Create a new `ParseState`. fn new( engine: &'e Engine, - externals: &'s mut Vec, #[cfg(not(feature = "unchecked"))] max_expr_depth: usize, #[cfg(not(feature = "unchecked"))] max_function_expr_depth: usize, ) -> Self { @@ -432,22 +432,7 @@ impl<'e, 's> ParseState<'e, 's> { engine, #[cfg(not(feature = "unchecked"))] max_expr_depth, #[cfg(not(feature = "unchecked"))] max_function_expr_depth, - externals, - stack: Default::default(), - modules: Default::default(), - } - } - - /// Creates a new `ParseState` with empty `stack` and `modules` lists, but - /// deriving other settings from the passed `ParseState` instance. - fn derive(&'s mut self) -> Self { - Self { - engine: self.engine, - #[cfg(not(feature = "unchecked"))] - max_expr_depth: self.max_expr_depth, - #[cfg(not(feature = "unchecked"))] - max_function_expr_depth: self.max_function_expr_depth, - externals: self.externals, + #[cfg(not(feature = "no_closures"))] externals: Default::default(), stack: Default::default(), modules: Default::default(), } @@ -463,7 +448,7 @@ impl<'e, 's> ParseState<'e, 's> { /// i.e. the top element of the `ParseState` is offset 1. /// Return `None` when the variable name is not found in the `stack`. fn access_var(&mut self, name: &str) -> Option { - let mut index = self.stack + let index = self.stack .iter() .rev() .enumerate() @@ -483,6 +468,7 @@ impl<'e, 's> ParseState<'e, 's> { } /// Creates a curry expression from a list of external variables + #[cfg(not(feature = "no_closures"))] fn make_curry_from_externals(&self, fn_expr: Expr, settings: &ParseSettings) -> Expr { if self.externals.is_empty() { return fn_expr @@ -1846,11 +1832,10 @@ fn parse_unary( // | ... #[cfg(not(feature = "no_function"))] Token::Pipe | Token::Or => { - let mut _externals = Default::default(); - let mut state = ParseState::new( + let mut new_state = ParseState::new( state.engine, - &mut _externals, - state.max_function_expr_depth, + #[cfg(not(feature = "unchecked"))] + state.max_expr_depth, #[cfg(not(feature = "unchecked"))] state.max_function_expr_depth, ); @@ -1866,7 +1851,12 @@ fn parse_unary( pos: *token_pos, }; - let (expr, func) = parse_anon_fn(input, &mut state, lib, settings)?; + let (expr, func) = parse_anon_fn(input, &mut new_state, lib, settings)?; + + #[cfg(not(feature = "no_closures"))] + for closure in new_state.externals { + state.access_var(&closure); + } // Qualifiers (none) + function name + number of arguments. let hash = calc_fn_hash(empty(), &func.name, func.params.len(), empty()); @@ -2891,12 +2881,10 @@ fn parse_stmt( match input.next().unwrap() { (Token::Fn, pos) => { - let mut _externals = Default::default(); let mut state = ParseState::new( state.engine, - &mut _externals, #[cfg(not(feature = "unchecked"))] - state.max_function_expr_depth, + state.max_expr_depth, #[cfg(not(feature = "unchecked"))] state.max_function_expr_depth, ); @@ -3195,11 +3183,15 @@ fn parse_anon_fn( pos: settings.pos, }; - let mut expr = state.make_curry_from_externals( + #[cfg(not(feature = "no_closures"))] + let expr = state.make_curry_from_externals( Expr::FnPointer(Box::new((fn_name, settings.pos))), &settings, ); + #[cfg(feature = "no_closures")] + let expr = Expr::FnPointer(Box::new((fn_name, settings.pos))); + Ok((expr, script)) } @@ -3211,10 +3203,8 @@ impl Engine { optimization_level: OptimizationLevel, ) -> Result { let mut functions = Default::default(); - let mut _externals = Default::default(); let mut state = ParseState::new( self, - &mut _externals, #[cfg(not(feature = "unchecked"))] self.limits.max_expr_depth, #[cfg(not(feature = "unchecked"))] @@ -3258,10 +3248,8 @@ impl Engine { ) -> Result<(Vec, Vec), ParseError> { let mut statements: Vec = Default::default(); let mut functions = Default::default(); - let mut _externals = Default::default(); let mut state = ParseState::new( self, - &mut _externals, #[cfg(not(feature = "unchecked"))] self.limits.max_expr_depth, #[cfg(not(feature = "unchecked"))] diff --git a/tests/call_fn.rs b/tests/call_fn.rs index e2b64dfe..74a703ae 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -211,22 +211,26 @@ fn test_fn_ptr_curry_call() -> Result<(), Box> { } #[test] +#[cfg(not(feature = "no_closures"))] fn test_fn_closures() -> Result<(), Box> { - let mut engine = Engine::new(); + let engine = Engine::new(); - let res = engine.eval::( - r#" - let x = 100; + assert_eq!( + engine.eval::( + r#" + let x = 8; - let f = || x; + let res = |y, z| { + let w = 12; - let x = 200; + return (|| x + y + z + w).call(); + }.curry(15).call(2); - f.call() - "# - ).unwrap(); - - panic!("{:#?}", res); + res + (|| x - 3).call() + "# + )?, + 42 + ); Ok(()) } From a856a8bbcb3ddb0b06855d133484d3e55680bb33 Mon Sep 17 00:00:00 2001 From: Ilya Lakhin Date: Wed, 29 Jul 2020 19:04:33 +0700 Subject: [PATCH 18/64] max_expr_depth bug fix --- src/parser.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 0ab09875..f273cab3 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1835,7 +1835,7 @@ fn parse_unary( let mut new_state = ParseState::new( state.engine, #[cfg(not(feature = "unchecked"))] - state.max_expr_depth, + state.max_function_expr_depth, #[cfg(not(feature = "unchecked"))] state.max_function_expr_depth, ); @@ -2884,7 +2884,7 @@ fn parse_stmt( let mut state = ParseState::new( state.engine, #[cfg(not(feature = "unchecked"))] - state.max_expr_depth, + state.max_function_expr_depth, #[cfg(not(feature = "unchecked"))] state.max_function_expr_depth, ); From 8fc16b40e6244a6022c2ff4ba98e7b3809093197 Mon Sep 17 00:00:00 2001 From: Ilya Lakhin Date: Wed, 29 Jul 2020 20:17:18 +0700 Subject: [PATCH 19/64] Code style issues fixed --- Cargo.toml | 2 +- src/parser.rs | 39 ++++++++++++++++++++------------------- tests/call_fn.rs | 2 +- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 606412c9..2c8df560 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ only_i64 = [] # set INT=i64 (default) and disable support for all other in no_index = [] # no arrays and indexing no_object = [] # no custom objects no_function = [] # no script-defined functions -no_closures = [] # no automatic read/write binding of anonymous function's local variables to it's external context +no_capture = [] # no automatic read/write binding of anonymous function's local variables to it's external context no_module = [] # no modules internals = [] # expose internal data structures unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. diff --git a/src/parser.rs b/src/parser.rs index f273cab3..0b545b8f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -15,7 +15,7 @@ use crate::utils::{StaticVec, StraightHasherBuilder}; #[cfg(not(feature = "no_function"))] use crate::engine::FN_ANONYMOUS; -#[cfg(not(feature = "no_closures"))] +#[cfg(not(feature = "no_capture"))] use crate::engine::KEYWORD_FN_PTR_CURRY; #[cfg(not(feature = "no_object"))] @@ -402,28 +402,29 @@ pub enum ReturnType { Exception, } +#[derive(Clone)] struct ParseState<'e> { - // Reference to the scripting `Engine`. + /// Reference to the scripting `Engine`. engine: &'e Engine, - // Encapsulates a local stack with variable names to simulate an actual runtime scope. + /// Encapsulates a local stack with variable names to simulate an actual runtime scope. stack: Vec<(String, ScopeEntryType)>, - // Tracks a list of external variables(variables that are not explicitly - // declared in the scope during AST evaluation). - #[cfg(not(feature = "no_closures"))] + /// Tracks a list of external variables(variables that are not explicitly + /// declared in the scope during AST evaluation). + #[cfg(not(feature = "no_capture"))] externals: Vec, - // Encapsulates a local stack with variable names to simulate an actual runtime scope. + /// Encapsulates a local stack with variable names to simulate an actual runtime scope. modules: Vec, - // Maximum levels of expression nesting. + /// Maximum levels of expression nesting. #[cfg(not(feature = "unchecked"))] max_expr_depth: usize, - // Maximum levels of expression nesting in functions. + /// Maximum levels of expression nesting in functions. #[cfg(not(feature = "unchecked"))] max_function_expr_depth: usize, } impl<'e> ParseState<'e> { /// Create a new `ParseState`. - fn new( + pub fn new( engine: &'e Engine, #[cfg(not(feature = "unchecked"))] max_expr_depth: usize, #[cfg(not(feature = "unchecked"))] max_function_expr_depth: usize, @@ -432,7 +433,7 @@ impl<'e> ParseState<'e> { engine, #[cfg(not(feature = "unchecked"))] max_expr_depth, #[cfg(not(feature = "unchecked"))] max_function_expr_depth, - #[cfg(not(feature = "no_closures"))] externals: Default::default(), + #[cfg(not(feature = "no_capture"))] externals: Default::default(), stack: Default::default(), modules: Default::default(), } @@ -459,7 +460,7 @@ impl<'e> ParseState<'e> { return index } - #[cfg(not(feature = "no_closures"))] + #[cfg(not(feature = "no_capture"))] if self.externals.iter().find(|n| *n == name).is_none() { self.externals.push(name.to_string()); } @@ -468,8 +469,8 @@ impl<'e> ParseState<'e> { } /// Creates a curry expression from a list of external variables - #[cfg(not(feature = "no_closures"))] - fn make_curry_from_externals(&self, fn_expr: Expr, settings: &ParseSettings) -> Expr { + #[cfg(not(feature = "no_capture"))] + pub fn make_curry_from_externals(&self, fn_expr: Expr, settings: &ParseSettings) -> Expr { if self.externals.is_empty() { return fn_expr } @@ -502,7 +503,7 @@ impl<'e> ParseState<'e> { /// The return value is the offset to be deducted from `Stack::len`, /// i.e. the top element of the `ParseState` is offset 1. /// Return `None` when the variable name is not found in the `ParseState`. - fn find_module(&self, name: &str) -> Option { + pub fn find_module(&self, name: &str) -> Option { self.modules .iter() .rev() @@ -1853,7 +1854,7 @@ fn parse_unary( let (expr, func) = parse_anon_fn(input, &mut new_state, lib, settings)?; - #[cfg(not(feature = "no_closures"))] + #[cfg(not(feature = "no_capture"))] for closure in new_state.externals { state.access_var(&closure); } @@ -3152,7 +3153,7 @@ fn parse_anon_fn( let mut static_params = StaticVec::::new(); - #[cfg(not(feature = "no_closures"))] + #[cfg(not(feature = "no_capture"))] for closure in state.externals.iter() { static_params.push(closure.clone()); } @@ -3183,13 +3184,13 @@ fn parse_anon_fn( pos: settings.pos, }; - #[cfg(not(feature = "no_closures"))] + #[cfg(not(feature = "no_capture"))] let expr = state.make_curry_from_externals( Expr::FnPointer(Box::new((fn_name, settings.pos))), &settings, ); - #[cfg(feature = "no_closures")] + #[cfg(feature = "no_capture")] let expr = Expr::FnPointer(Box::new((fn_name, settings.pos))); Ok((expr, script)) diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 74a703ae..d603f743 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -211,7 +211,7 @@ fn test_fn_ptr_curry_call() -> Result<(), Box> { } #[test] -#[cfg(not(feature = "no_closures"))] +#[cfg(not(feature = "no_capture"))] fn test_fn_closures() -> Result<(), Box> { let engine = Engine::new(); From 1465ba2315ad3cf6db0a204df5838589323171e4 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 29 Jul 2020 22:43:50 +0800 Subject: [PATCH 20/64] Refactor auto-currying. --- src/lib.rs | 18 +------ src/optimize.rs | 1 - src/parser.rs | 132 ++++++++++++++++++++++++------------------------ src/utils.rs | 3 ++ 4 files changed, 71 insertions(+), 83 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1194fd67..7406b45a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,23 +48,7 @@ //! } //! ``` //! -//! ## Optional features -//! -//! | Feature | Description | -//! | ------------- | ----------------------------------------------------------------------------------------------------------------------------------| -//! | `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! | -//! | `no_function` | Disable script-defined functions if not needed. | -//! | `no_module` | Disable loading external modules if not needed. | -//! | `no_index` | Disable arrays and indexing features if not needed. | -//! | `no_object` | Disable support for custom types and objects. | -//! | `no_float` | Disable floating-point numbers and math if not needed. | -//! | `no_optimize` | Disable the script optimizer. | -//! | `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | -//! | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | -//! | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | -//! | `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, `Scope` and `AST` are all `Send + Sync`. | -//! | `serde` | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. | -//! | `internals` | Expose internal data structures (beware they may be volatile from version to version). | +//! # Documentation //! //! See [The Rhai Book](https://schungx.github.io/rhai) for details on the Rhai script engine and language. diff --git a/src/optimize.rs b/src/optimize.rs index c047cd1b..b46b7488 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -20,7 +20,6 @@ use crate::parser::CustomExpr; use crate::stdlib::{ boxed::Box, iter::empty, - mem, string::{String, ToString}, vec, vec::Vec, diff --git a/src/parser.rs b/src/parser.rs index 5b29f3d0..49429040 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -25,7 +25,7 @@ use crate::stdlib::{ borrow::Cow, boxed::Box, char, - collections::HashMap, + collections::{HashMap, HashSet}, fmt, format, hash::{Hash, Hasher}, iter::empty, @@ -408,10 +408,9 @@ struct ParseState<'e> { engine: &'e Engine, /// Encapsulates a local stack with variable names to simulate an actual runtime scope. stack: Vec<(String, ScopeEntryType)>, - /// Tracks a list of external variables(variables that are not explicitly - /// declared in the scope during AST evaluation). + /// Tracks a list of external variables (variables that are not explicitly declared in the scope). #[cfg(not(feature = "no_capture"))] - externals: Vec, + externals: HashSet<(String, Position)>, /// Encapsulates a local stack with variable names to simulate an actual runtime scope. modules: Vec, /// Maximum levels of expression nesting. @@ -431,72 +430,39 @@ impl<'e> ParseState<'e> { ) -> Self { Self { engine, - #[cfg(not(feature = "unchecked"))] max_expr_depth, - #[cfg(not(feature = "unchecked"))] max_function_expr_depth, - #[cfg(not(feature = "no_capture"))] externals: Default::default(), + #[cfg(not(feature = "unchecked"))] + max_expr_depth, + #[cfg(not(feature = "unchecked"))] + max_function_expr_depth, + #[cfg(not(feature = "no_capture"))] + externals: Default::default(), stack: Default::default(), modules: Default::default(), } } - /// Find explicitly declared variable by name in the `ParseState`, - /// searching in reverse order. + /// Find explicitly declared variable by name in the `ParseState`, searching in reverse order. /// - /// If the variable is not present in the scope adds it to the list of - /// external variables + /// If the variable is not present in the scope adds it to the list of external variables /// /// The return value is the offset to be deducted from `Stack::len`, /// i.e. the top element of the `ParseState` is offset 1. /// Return `None` when the variable name is not found in the `stack`. - fn access_var(&mut self, name: &str) -> Option { - let index = self.stack + fn access_var(&mut self, name: &str, pos: Position) -> Option { + let index = self + .stack .iter() .rev() .enumerate() .find(|(_, (n, _))| *n == name) .and_then(|(i, _)| NonZeroUsize::new(i + 1)); - if index.is_some() { - return index - } - #[cfg(not(feature = "no_capture"))] - if self.externals.iter().find(|n| *n == name).is_none() { - self.externals.push(name.to_string()); + if index.is_none() { + self.externals.insert((name.to_string(), pos)); } - None - } - - /// Creates a curry expression from a list of external variables - #[cfg(not(feature = "no_capture"))] - pub fn make_curry_from_externals(&self, fn_expr: Expr, settings: &ParseSettings) -> Expr { - if self.externals.is_empty() { - return fn_expr - } - - let mut args = StaticVec::new(); - - for var in self.externals.iter() { - args.push(Expr::Variable(Box::new(( - (var.clone(), settings.pos), - None, - 0, - None, - )))); - } - - Expr::Dot(Box::new(( - fn_expr, - Expr::FnCall(Box::new(( - (KEYWORD_FN_PTR_CURRY.into(), false, settings.pos), - None, - calc_fn_hash(empty(), KEYWORD_FN_PTR_CURRY, self.externals.len(), empty()), - args, - None, - ))), - settings.pos, - ))) + index } /// Find a module by name in the `ParseState`, searching in reverse. @@ -1632,7 +1598,7 @@ fn parse_primary( Token::CharConstant(c) => Expr::CharConstant(Box::new((c, settings.pos))), Token::StringConstant(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))), Token::Identifier(s) => { - let index = state.access_var(&s); + let index = state.access_var(&s, settings.pos); Expr::Variable(Box::new(((s, settings.pos), None, 0, index))) } // Function call is allowed to have reserved keyword @@ -1855,9 +1821,9 @@ fn parse_unary( let (expr, func) = parse_anon_fn(input, &mut new_state, lib, settings)?; #[cfg(not(feature = "no_capture"))] - for closure in new_state.externals { - state.access_var(&closure); - } + new_state.externals.iter().for_each(|(closure, pos)| { + state.access_var(closure, *pos); + }); // Qualifiers (none) + function name + number of arguments. let hash = calc_fn_hash(empty(), &func.name, func.params.len(), empty()); @@ -3084,6 +3050,46 @@ fn parse_fn( }) } +/// Creates a curried expression from a list of external variables +#[cfg(not(feature = "no_capture"))] +fn make_curry_from_externals( + fn_expr: Expr, + state: &mut ParseState, + settings: &ParseSettings, +) -> Expr { + if state.externals.is_empty() { + return fn_expr; + } + + let mut args: StaticVec<_> = Default::default(); + + state.externals.iter().for_each(|(var_name, pos)| { + args.push(Expr::Variable(Box::new(( + (var_name.clone(), *pos), + None, + 0, + None, + )))); + }); + + let hash = calc_fn_hash( + empty(), + KEYWORD_FN_PTR_CURRY, + state.externals.len(), + empty(), + ); + + let fn_call = Expr::FnCall(Box::new(( + (KEYWORD_FN_PTR_CURRY.into(), false, settings.pos), + None, + hash, + args, + None, + ))); + + Expr::Dot(Box::new((fn_expr, fn_call, settings.pos))) +} + /// Parse an anonymous function definition. #[cfg(not(feature = "no_function"))] fn parse_anon_fn( @@ -3151,12 +3157,12 @@ fn parse_anon_fn( let body = parse_stmt(input, state, lib, settings.level_up()) .map(|stmt| stmt.unwrap_or_else(|| Stmt::Noop(pos)))?; - let mut static_params = StaticVec::::new(); + let mut static_params: StaticVec<_> = Default::default(); #[cfg(not(feature = "no_capture"))] - for closure in state.externals.iter() { + state.externals.iter().for_each(|(closure, _)| { static_params.push(closure.clone()); - } + }); for param in params.into_iter() { static_params.push(param.0); @@ -3184,15 +3190,11 @@ fn parse_anon_fn( pos: settings.pos, }; - #[cfg(not(feature = "no_capture"))] - let expr = state.make_curry_from_externals( - Expr::FnPointer(Box::new((fn_name, settings.pos))), - &settings, - ); - - #[cfg(feature = "no_capture")] let expr = Expr::FnPointer(Box::new((fn_name, settings.pos))); + #[cfg(not(feature = "no_capture"))] + let expr = make_curry_from_externals(expr, state, &settings); + Ok((expr, script)) } diff --git a/src/utils.rs b/src/utils.rs index a6cfeb26..13e11704 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -87,6 +87,9 @@ pub fn calc_fn_spec<'a>( s.finish() } +/// [INTERNALS] Alias to [`smallvec::SmallVec<[T; 4]>`](https://crates.io/crates/smallvec), +/// which is a specialized `Vec` backed by a small, fixed-size array when there are <= 4 items stored. +/// Exported under the `internals` feature only. pub type StaticVec = SmallVec<[T; 4]>; /// The system immutable string type. From 8299adf95c5cffd3d40f5de94e309b6822e76196 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 29 Jul 2020 22:43:57 +0800 Subject: [PATCH 21/64] Add docs for auto-currying. --- doc/src/SUMMARY.md | 1 + doc/src/engine/dsl.md | 11 ++++ doc/src/language/fn-anon.md | 4 ++ doc/src/language/fn-closure.md | 54 +++++++++++++++++ doc/src/language/fn-curry.md | 9 +++ doc/src/language/oop.md | 12 ++++ doc/src/links.md | 3 + doc/src/start/features.md | 1 + tests/call_fn.rs | 103 ++++++++------------------------- tests/closures.rs | 60 +++++++++++++++++++ 10 files changed, 178 insertions(+), 80 deletions(-) create mode 100644 doc/src/language/fn-closure.md create mode 100644 tests/closures.rs diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index 3592dbc5..78ea9882 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -79,6 +79,7 @@ The Rhai Scripting Language 4. [Function Pointers](language/fn-ptr.md) 5. [Anonymous Functions](language/fn-anon.md) 6. [Currying](language/fn-curry.md) + 7. [Capturing External Variables](language/fn-closure.md) 16. [Print and Debug](language/print-debug.md) 17. [Modules](language/modules/index.md) 1. [Export Variables, Functions and Sub-Modules](language/modules/export.md) diff --git a/doc/src/engine/dsl.md b/doc/src/engine/dsl.md index 5b381d4a..8f326ed1 100644 --- a/doc/src/engine/dsl.md +++ b/doc/src/engine/dsl.md @@ -15,6 +15,17 @@ The [`Engine::eval_expression_XXX`][`eval_expression`] API can be used to restri a script to expressions only. +Unicode Standard Annex #31 Identifiers +------------------------------------- + +Variable names and other identifiers do not necessarily need to be ASCII-only. + +The [`unicode-xid-ident`] feature, when turned on, causes Rhai to allow variable names and identifiers +that follow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/). + +This is sometimes useful in a non-English DSL. + + Disable Keywords and/or Operators -------------------------------- diff --git a/doc/src/language/fn-anon.md b/doc/src/language/fn-anon.md index 21ceb7a3..a4cb0fee 100644 --- a/doc/src/language/fn-anon.md +++ b/doc/src/language/fn-anon.md @@ -49,9 +49,13 @@ fn anon_fn_1001(x) { this.data -= x; } fn anon_fn_1002() { print this.data; } ``` + WARNING - NOT Closures ---------------------- Remember: anonymous functions, though having the same syntax as Rust _closures_, are themselves **not** closures. In particular, they do not capture their running environment. They are more like Rust's function pointers. + +They do, however, _capture_ variable _values_ from their execution environment, unless the [`no_capture`] +feature is turned on. This is accomplished via [automatic currying][capture]. diff --git a/doc/src/language/fn-closure.md b/doc/src/language/fn-closure.md new file mode 100644 index 00000000..d8749601 --- /dev/null +++ b/doc/src/language/fn-closure.md @@ -0,0 +1,54 @@ +Capture External Variables via Automatic Currying +================================================ + +Poor Man's Closures +------------------- + +Since [anonymous functions] de-sugar to standard function definitions, they retain all the behaviors of +Rhai functions, including being _pure_, having no access to external variables. + +The anonymous function syntax, however, automatically _captures_ variables that are not defined within +the current scope, but are defined in the external scope - i.e. the scope where the anonymous function +is created. + +Variables that are accessible during the time the [anonymous function] is created can be captured, +as long as they are not shadowed by local variables defined within the function's scope. +The values captured are the values of those variables at the time of the [anonymous function]'s creation. + + +New Parameters For Captured Variables +------------------------------------ + +In actual implementation, this de-sugars to: + +1. Keeping track of what variables are accessed inside the anonymous function, + +2. If a variable is not defined within the anonymous function's scope, it is looked up _outside_ the function and in the current execution scope - where the anonymous function is created. + +3. The variable is added to the parameters list of the anonymous function, at the front. + +4. The current value of the variable is then [curried][currying] into the [function pointer] itself, essentially carrying that value and inserting it into future calls of the function. + +Automatic currying can be turned off via the [`no_capture`] feature. + + +Examples +-------- + +```rust +let x = 40; + +let f = |y| x + y; // current value of variable 'x' is auto-curried + // the value 40 is curried into 'f' + +x = 1; // 'x' can be changed but the curried value is not + +f.call(2) == 42; // the value of 'x' is still 40 + +// The above de-sugars into this: +fn anon$1001(x, y) { x + y } // parameter 'x' is inserted + +let f = Fn("anon$1001").curry(x); // current value of 'x' is curried + +f.call(2) == 42; +``` diff --git a/doc/src/language/fn-curry.md b/doc/src/language/fn-curry.md index 8ee103a9..71c8933a 100644 --- a/doc/src/language/fn-curry.md +++ b/doc/src/language/fn-curry.md @@ -28,3 +28,12 @@ let curried = curry(func, 21); // function-call style also works curried.call(2) == 42; // <- de-sugars to 'func.call(21, 2)' // only one argument is now required ``` + + +Automatic Currying +------------------ + +[Anonymous functions] defined via a closure syntax _capture_ external variables that are not shadowed inside +the function's scope. + +This is accomplished via [automatic currying]. diff --git a/doc/src/language/oop.md b/doc/src/language/oop.md index e34a8ab2..09bc7b55 100644 --- a/doc/src/language/oop.md +++ b/doc/src/language/oop.md @@ -21,6 +21,18 @@ When a property of an [object map] is called like a method function, and if it h a valid [function pointer] (perhaps defined via an [anonymous function]), then the call will be dispatched to the actual function with `this` binding to the [object map] itself. + +Use Anonymous Functions to Define Methods +---------------------------------------- + +[Anonymous functions] defined as values for [object map] properties take on a syntactic shape +that resembles very closely that of class methods in an OOP language. + +Anonymous functions can also _capture_ variables from the defining environment, which is a very +common OOP pattern. Capturing is accomplished via a feature called _[automatic currying]_ and +can be turned off via the [`no_capture`] feature. + + Examples -------- diff --git a/doc/src/links.md b/doc/src/links.md index 936c714d..19fdf0e9 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -9,6 +9,7 @@ [`no_object`]: {{rootUrl}}/start/features.md [`no_function`]: {{rootUrl}}/start/features.md [`no_module`]: {{rootUrl}}/start/features.md +[`no_capture`]: {{rootUrl}}/start/features.md [`no_std`]: {{rootUrl}}/start/features.md [`no-std`]: {{rootUrl}}/start/features.md [`internals`]: {{rootUrl}}/start/features.md @@ -78,6 +79,8 @@ [function pointer]: {{rootUrl}}/language/fn-ptr.md [function pointers]: {{rootUrl}}/language/fn-ptr.md [currying]: {{rootUrl}}/language/fn-curry.md +[capture]: {{rootUrl}}/language/fn-closure.md +[automatic currying]: {{rootUrl}}/language/fn-closure.md [function namespace]: {{rootUrl}}/language/fn-namespaces.md [function namespaces]: {{rootUrl}}/language/fn-namespaces.md [anonymous function]: {{rootUrl}}/language/fn-anon.md diff --git a/doc/src/start/features.md b/doc/src/start/features.md index 0f6b0f53..7a06618a 100644 --- a/doc/src/start/features.md +++ b/doc/src/start/features.md @@ -23,6 +23,7 @@ more control over what a script can (or cannot) do. | `no_object` | Disable support for [custom types] and [object maps]. | | `no_function` | Disable script-defined [functions]. | | `no_module` | Disable loading external [modules]. | +| `no_capture` | Disable capturing external variables in [anonymous functions]. | | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | | `serde` | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. | | `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. | diff --git a/tests/call_fn.rs b/tests/call_fn.rs index d603f743..0f9087d2 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -83,38 +83,6 @@ fn test_call_fn_private() -> Result<(), Box> { Ok(()) } -#[test] -fn test_anonymous_fn() -> Result<(), Box> { - let calc_func = Func::<(INT, INT, INT), INT>::create_from_script( - Engine::new(), - "fn calc(x, y, z,) { (x + y) * z }", - "calc", - )?; - - assert_eq!(calc_func(42, 123, 9)?, 1485); - - let calc_func = Func::<(INT, String, INT), INT>::create_from_script( - Engine::new(), - "fn calc(x, y, z) { (x + len(y)) * z }", - "calc", - )?; - - assert_eq!(calc_func(42, "hello".to_string(), 9)?, 423); - - let calc_func = Func::<(INT, INT, INT), INT>::create_from_script( - Engine::new(), - "private fn calc(x, y, z) { (x + y) * z }", - "calc", - )?; - - assert!(matches!( - *calc_func(42, 123, 9).expect_err("should error"), - EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "calc" - )); - - Ok(()) -} - #[test] #[cfg(not(feature = "no_object"))] fn test_fn_ptr_raw() -> Result<(), Box> { @@ -179,58 +147,33 @@ fn test_fn_ptr_raw() -> Result<(), Box> { } #[test] -fn test_fn_ptr_curry_call() -> Result<(), Box> { - let mut module = Module::new(); +fn test_anonymous_fn() -> Result<(), Box> { + let calc_func = Func::<(INT, INT, INT), INT>::create_from_script( + Engine::new(), + "fn calc(x, y, z,) { (x + y) * z }", + "calc", + )?; - module.set_raw_fn( - "call_with_arg", - &[TypeId::of::(), TypeId::of::()], - |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { - let fn_ptr = std::mem::take(args[0]).cast::(); - fn_ptr.call_dynamic(engine, lib, None, [std::mem::take(args[1])]) - }, - ); + assert_eq!(calc_func(42, 123, 9)?, 1485); - let mut engine = Engine::new(); - engine.load_package(module.into()); + let calc_func = Func::<(INT, String, INT), INT>::create_from_script( + Engine::new(), + "fn calc(x, y, z) { (x + len(y)) * z }", + "calc", + )?; - #[cfg(not(feature = "no_object"))] - assert_eq!( - engine.eval::( - r#" - let addition = |x, y| { x + y }; - let curried = addition.curry(2); + assert_eq!(calc_func(42, "hello".to_string(), 9)?, 423); - call_with_arg(curried, 40) - "# - )?, - 42 - ); - - Ok(()) -} - -#[test] -#[cfg(not(feature = "no_capture"))] -fn test_fn_closures() -> Result<(), Box> { - let engine = Engine::new(); - - assert_eq!( - engine.eval::( - r#" - let x = 8; - - let res = |y, z| { - let w = 12; - - return (|| x + y + z + w).call(); - }.curry(15).call(2); - - res + (|| x - 3).call() - "# - )?, - 42 - ); + let calc_func = Func::<(INT, INT, INT), INT>::create_from_script( + Engine::new(), + "private fn calc(x, y, z) { (x + y) * z }", + "calc", + )?; + + assert!(matches!( + *calc_func(42, 123, 9).expect_err("should error"), + EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "calc" + )); Ok(()) } diff --git a/tests/closures.rs b/tests/closures.rs new file mode 100644 index 00000000..3aec4865 --- /dev/null +++ b/tests/closures.rs @@ -0,0 +1,60 @@ +#![cfg(not(feature = "no_function"))] +use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, Module, INT}; +use std::any::TypeId; + +#[test] +fn test_fn_ptr_curry_call() -> Result<(), Box> { + let mut module = Module::new(); + + module.set_raw_fn( + "call_with_arg", + &[TypeId::of::(), TypeId::of::()], + |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { + let fn_ptr = std::mem::take(args[0]).cast::(); + fn_ptr.call_dynamic(engine, lib, None, [std::mem::take(args[1])]) + }, + ); + + let mut engine = Engine::new(); + engine.load_package(module.into()); + + #[cfg(not(feature = "no_object"))] + assert_eq!( + engine.eval::( + r#" + let addition = |x, y| { x + y }; + let curried = addition.curry(2); + + call_with_arg(curried, 40) + "# + )?, + 42 + ); + + Ok(()) +} + +#[test] +#[cfg(not(feature = "no_capture"))] +fn test_closures() -> Result<(), Box> { + let engine = Engine::new(); + + assert_eq!( + engine.eval::( + r#" + let x = 8; + + let res = |y, z| { + let w = 12; + + return (|| x + y + z + w).call(); + }.curry(15).call(2); + + res + (|| x - 3).call() + "# + )?, + 42 + ); + + Ok(()) +} From 721c578407bf197ba79c1b07d988aaaef29d61b2 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 29 Jul 2020 23:34:48 +0800 Subject: [PATCH 22/64] Use HashMap for externals. --- src/parser.rs | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 49429040..3068bad0 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -25,7 +25,7 @@ use crate::stdlib::{ borrow::Cow, boxed::Box, char, - collections::{HashMap, HashSet}, + collections::HashMap, fmt, format, hash::{Hash, Hasher}, iter::empty, @@ -410,7 +410,7 @@ struct ParseState<'e> { stack: Vec<(String, ScopeEntryType)>, /// Tracks a list of external variables (variables that are not explicitly declared in the scope). #[cfg(not(feature = "no_capture"))] - externals: HashSet<(String, Position)>, + externals: HashMap, /// Encapsulates a local stack with variable names to simulate an actual runtime scope. modules: Vec, /// Maximum levels of expression nesting. @@ -458,8 +458,8 @@ impl<'e> ParseState<'e> { .and_then(|(i, _)| NonZeroUsize::new(i + 1)); #[cfg(not(feature = "no_capture"))] - if index.is_none() { - self.externals.insert((name.to_string(), pos)); + if index.is_none() && !self.externals.contains_key(name) { + self.externals.insert(name.to_string(), pos); } index @@ -3157,16 +3157,17 @@ fn parse_anon_fn( let body = parse_stmt(input, state, lib, settings.level_up()) .map(|stmt| stmt.unwrap_or_else(|| Stmt::Noop(pos)))?; - let mut static_params: StaticVec<_> = Default::default(); + #[cfg(feature = "no_capture")] + let params: StaticVec<_> = params.into_iter().map(|(v, _)| v).collect(); + // Add parameters that are auto-curried #[cfg(not(feature = "no_capture"))] - state.externals.iter().for_each(|(closure, _)| { - static_params.push(closure.clone()); - }); - - for param in params.into_iter() { - static_params.push(param.0); - } + let params: StaticVec<_> = state + .externals + .keys() + .cloned() + .chain(params.into_iter().map(|(v, _)| v)) + .collect(); // Calculate hash #[cfg(feature = "no_std")] @@ -3174,8 +3175,8 @@ fn parse_anon_fn( #[cfg(not(feature = "no_std"))] let mut s = DefaultHasher::new(); - s.write_usize(static_params.len()); - static_params.iter().for_each(|a| a.hash(&mut s)); + s.write_usize(params.len()); + params.iter().for_each(|a| a.hash(&mut s)); body.hash(&mut s); let hash = s.finish(); @@ -3185,7 +3186,7 @@ fn parse_anon_fn( let script = ScriptFnDef { name: fn_name.clone(), access: FnAccess::Public, - params: static_params, + params, body, pos: settings.pos, }; From 691e04292fe8b8a29f15c5b2281c535ebf99e594 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 30 Jul 2020 13:28:06 +0800 Subject: [PATCH 23/64] Put externals in ScriptFnDef. --- src/optimize.rs | 30 +++++++++++++-------- src/parser.rs | 71 ++++++++++++++++++++++++++++++------------------- 2 files changed, 63 insertions(+), 38 deletions(-) diff --git a/src/optimize.rs b/src/optimize.rs index b46b7488..e26d1908 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -5,10 +5,10 @@ use crate::calc_fn_hash; use crate::engine::{ Engine, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF, }; +use crate::fn_native::FnPtr; use crate::module::Module; use crate::parser::{map_dynamic_to_expr, Expr, ScriptFnDef, Stmt, AST}; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; -use crate::token::is_valid_identifier; use crate::utils::StaticVec; #[cfg(not(feature = "no_function"))] @@ -19,6 +19,7 @@ use crate::parser::CustomExpr; use crate::stdlib::{ boxed::Box, + convert::TryFrom, iter::empty, string::{String, ToString}, vec, @@ -418,7 +419,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // All other items can be thrown away. state.set_dirty(); let pos = m.1; - m.0.into_iter().find(|((name, _), _)| name.as_str() == prop.as_str()) + m.0.into_iter().find(|((name, _), _)| name == prop) .map(|(_, mut expr)| { expr.set_position(pos); expr }) .unwrap_or_else(|| Expr::Unit(pos)) } @@ -495,7 +496,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { state.set_dirty(); let ch = a.0.to_string(); - if b.0.iter().find(|((name, _), _)| name.as_str() == ch.as_str()).is_some() { + if b.0.iter().find(|((name, _), _)| name == &ch).is_some() { Expr::True(a.1) } else { Expr::False(a.1) @@ -553,14 +554,19 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // Fn("...") Expr::FnCall(x) - if x.1.is_none() - && (x.0).0 == KEYWORD_FN_PTR - && x.3.len() == 1 - && matches!(x.3[0], Expr::StringConstant(_)) + if x.1.is_none() + && (x.0).0 == KEYWORD_FN_PTR + && x.3.len() == 1 + && matches!(x.3[0], Expr::StringConstant(_)) => { - match &x.3[0] { - Expr::StringConstant(s) if is_valid_identifier(s.0.chars()) => Expr::FnPointer(s.clone()), - _ => Expr::FnCall(x) + if let Expr::StringConstant(s) = &x.3[0] { + if let Ok(fn_ptr) = FnPtr::try_from(s.0.as_str()) { + Expr::FnPointer(Box::new((fn_ptr.take_data().0, s.1))) + } else { + Expr::FnCall(x) + } + } else { + unreachable!() } } @@ -578,7 +584,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { let _has_script_fn = state.lib.iter_fn().find(|(_, _, _, f)| { if !f.is_script() { return false; } let fn_def = f.get_fn_def(); - fn_def.name.as_str() == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len()) + fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len()) }).is_some(); #[cfg(feature = "no_function")] @@ -765,6 +771,8 @@ pub fn optimize_into_ast( access: fn_def.access, body: Default::default(), params: fn_def.params.clone(), + #[cfg(not(feature = "no_capture"))] + externals: fn_def.externals.clone(), pos: fn_def.pos, } .into() diff --git a/src/parser.rs b/src/parser.rs index 3068bad0..abe3c7d8 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -363,6 +363,9 @@ pub struct ScriptFnDef { pub access: FnAccess, /// Names of function parameters. pub params: StaticVec, + /// Access to external variables. + #[cfg(not(feature = "no_capture"))] + pub externals: StaticVec, /// Function body. pub body: Stmt, /// Position of the function definition. @@ -448,7 +451,7 @@ impl<'e> ParseState<'e> { /// The return value is the offset to be deducted from `Stack::len`, /// i.e. the top element of the `ParseState` is offset 1. /// Return `None` when the variable name is not found in the `stack`. - fn access_var(&mut self, name: &str, pos: Position) -> Option { + fn access_var(&mut self, name: &str, _pos: Position) -> Option { let index = self .stack .iter() @@ -459,7 +462,7 @@ impl<'e> ParseState<'e> { #[cfg(not(feature = "no_capture"))] if index.is_none() && !self.externals.contains_key(name) { - self.externals.insert(name.to_string(), pos); + self.externals.insert(name.to_string(), _pos); } index @@ -2848,7 +2851,7 @@ fn parse_stmt( match input.next().unwrap() { (Token::Fn, pos) => { - let mut state = ParseState::new( + let mut new_state = ParseState::new( state.engine, #[cfg(not(feature = "unchecked"))] state.max_function_expr_depth, @@ -2867,7 +2870,7 @@ fn parse_stmt( pos: pos, }; - let func = parse_fn(input, &mut state, lib, access, settings)?; + let func = parse_fn(input, &mut new_state, lib, access, settings)?; // Qualifiers (none) + function name + number of arguments. let hash = calc_fn_hash(empty(), &func.name, func.params.len(), empty()); @@ -3039,12 +3042,23 @@ fn parse_fn( (_, pos) => return Err(PERR::FnMissingBody(name).into_err(*pos)), }; - let params = params.into_iter().map(|(p, _)| p).collect(); + let params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect(); + + #[cfg(not(feature = "no_capture"))] + let externals = state + .externals + .iter() + .map(|(name, _)| name) + .filter(|name| !params.contains(name)) + .cloned() + .collect(); Ok(ScriptFnDef { name: name.into(), access, params, + #[cfg(not(feature = "no_capture"))] + externals, body, pos: settings.pos, }) @@ -3054,40 +3068,31 @@ fn parse_fn( #[cfg(not(feature = "no_capture"))] fn make_curry_from_externals( fn_expr: Expr, - state: &mut ParseState, - settings: &ParseSettings, + externals: StaticVec<(String, Position)>, + pos: Position, ) -> Expr { - if state.externals.is_empty() { + if externals.is_empty() { return fn_expr; } + let num_externals = externals.len(); let mut args: StaticVec<_> = Default::default(); - state.externals.iter().for_each(|(var_name, pos)| { - args.push(Expr::Variable(Box::new(( - (var_name.clone(), *pos), - None, - 0, - None, - )))); + externals.into_iter().for_each(|(var_name, pos)| { + args.push(Expr::Variable(Box::new(((var_name, pos), None, 0, None)))); }); - let hash = calc_fn_hash( - empty(), - KEYWORD_FN_PTR_CURRY, - state.externals.len(), - empty(), - ); + let hash = calc_fn_hash(empty(), KEYWORD_FN_PTR_CURRY, num_externals, empty()); let fn_call = Expr::FnCall(Box::new(( - (KEYWORD_FN_PTR_CURRY.into(), false, settings.pos), + (KEYWORD_FN_PTR_CURRY.into(), false, pos), None, hash, args, None, ))); - Expr::Dot(Box::new((fn_expr, fn_call, settings.pos))) + Expr::Dot(Box::new((fn_expr, fn_call, pos))) } /// Parse an anonymous function definition. @@ -3160,11 +3165,20 @@ fn parse_anon_fn( #[cfg(feature = "no_capture")] let params: StaticVec<_> = params.into_iter().map(|(v, _)| v).collect(); + // External variables may need to be processed in a consistent order, + // so extract them into a list. + #[cfg(not(feature = "no_capture"))] + let externals: StaticVec<_> = state + .externals + .iter() + .map(|(k, &v)| (k.clone(), v)) + .collect(); + // Add parameters that are auto-curried #[cfg(not(feature = "no_capture"))] - let params: StaticVec<_> = state - .externals - .keys() + let params: StaticVec<_> = externals + .iter() + .map(|(k, _)| k) .cloned() .chain(params.into_iter().map(|(v, _)| v)) .collect(); @@ -3183,10 +3197,13 @@ fn parse_anon_fn( // Create unique function name let fn_name: ImmutableString = format!("{}{:16x}", FN_ANONYMOUS, hash).into(); + // Define the function let script = ScriptFnDef { name: fn_name.clone(), access: FnAccess::Public, params, + #[cfg(not(feature = "no_capture"))] + externals: Default::default(), body, pos: settings.pos, }; @@ -3194,7 +3211,7 @@ fn parse_anon_fn( let expr = Expr::FnPointer(Box::new((fn_name, settings.pos))); #[cfg(not(feature = "no_capture"))] - let expr = make_curry_from_externals(expr, state, &settings); + let expr = make_curry_from_externals(expr, externals, settings.pos); Ok((expr, script)) } From e505a068397a52f44174b27443c3d64e68276982 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 30 Jul 2020 13:28:25 +0800 Subject: [PATCH 24/64] Add comparison operators to ImmutableString. --- src/engine.rs | 8 +++----- src/fn_call.rs | 3 ++- src/packages/map_basic.rs | 6 +++--- src/utils.rs | 43 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 7806382f..2ad03365 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1105,7 +1105,7 @@ impl Engine { .downcast_ref::() .ok_or_else(|| EvalAltResult::ErrorStringIndexExpr(idx_pos))?; - map.get_mut(index.as_str()) + map.get_mut(index) .map(Target::from) .unwrap_or_else(|| Target::from(())) }) @@ -1208,10 +1208,8 @@ 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_str()).into()), - Dynamic(Union::Char(c)) => { - Ok(rhs_value.contains_key(c.to_string().as_str()).into()) - } + Dynamic(Union::Str(s)) => Ok(rhs_value.contains_key(&s).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 { diff --git a/src/fn_call.rs b/src/fn_call.rs index 162410f3..861f490b 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -665,7 +665,8 @@ impl Engine { )) }) .and_then(|s| FnPtr::try_from(s)) - .map(Into::::into); + .map(Into::::into) + .map_err(|err| err.new_position(expr.position())); } } diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index cc1aa000..9437bc67 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -23,7 +23,7 @@ fn map_get_values(map: &mut Map) -> FuncReturn> { def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { lib.set_fn_2_mut( "has", - |map: &mut Map, prop: ImmutableString| Ok(map.contains_key(prop.as_str())), + |map: &mut Map, prop: ImmutableString| Ok(map.contains_key(&prop)), ); lib.set_fn_1_mut("len", |map: &mut Map| Ok(map.len() as INT)); lib.set_fn_1_mut("clear", |map: &mut Map| { @@ -32,7 +32,7 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { }); lib.set_fn_2_mut( "remove", - |x: &mut Map, name: ImmutableString| Ok(x.remove(name.as_str()).unwrap_or_else(|| ().into())), + |x: &mut Map, name: ImmutableString| Ok(x.remove(&name).unwrap_or_else(|| ().into())), ); lib.set_fn_2_mut( "mixin", @@ -47,7 +47,7 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { "fill_with", |map1: &mut Map, map2: Map| { map2.into_iter().for_each(|(key, value)| { - if !map1.contains_key(key.as_str()) { + if !map1.contains_key(&key) { map1.insert(key, value); } }); diff --git a/src/utils.rs b/src/utils.rs index 13e11704..6668a7a9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -6,6 +6,7 @@ use crate::stdlib::{ any::TypeId, borrow::Borrow, boxed::Box, + cmp::Ordering, fmt, hash::{BuildHasher, Hash, Hasher}, iter::FromIterator, @@ -141,6 +142,12 @@ impl AsRef for ImmutableString { } } +impl Borrow for ImmutableString { + fn borrow(&self) -> &String { + &self.0 + } +} + impl Borrow for ImmutableString { fn borrow(&self) -> &str { self.0.as_str() @@ -348,6 +355,42 @@ impl AddAssign for ImmutableString { } } +impl> PartialEq for ImmutableString { + fn eq(&self, other: &S) -> bool { + self.as_str().eq(other.as_ref()) + } +} + +impl PartialEq for str { + fn eq(&self, other: &ImmutableString) -> bool { + self.eq(other.as_str()) + } +} + +impl PartialEq for String { + fn eq(&self, other: &ImmutableString) -> bool { + self.eq(other.as_str()) + } +} + +impl> PartialOrd for ImmutableString { + fn partial_cmp(&self, other: &S) -> Option { + self.as_str().partial_cmp(other.as_ref()) + } +} + +impl PartialOrd for str { + fn partial_cmp(&self, other: &ImmutableString) -> Option { + self.partial_cmp(other.as_str()) + } +} + +impl PartialOrd for String { + fn partial_cmp(&self, other: &ImmutableString) -> Option { + self.as_str().partial_cmp(other.as_str()) + } +} + impl ImmutableString { /// Consume the `ImmutableString` and convert it into a `String`. /// If there are other references to the same string, a cloned copy is returned. From 98b294c699a163a74551eda7a3a88dfb1e16e9ae Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 30 Jul 2020 18:18:28 +0800 Subject: [PATCH 25/64] Implement capturing. --- RELEASES.md | 4 + doc/src/SUMMARY.md | 13 +- doc/src/advanced.md | 2 + doc/src/language/fn-anon.md | 2 +- doc/src/language/fn-capture.md | 62 +++++++ doc/src/language/fn-closure.md | 2 + doc/src/language/fn-curry.md | 4 +- doc/src/start/features.md | 2 +- src/engine.rs | 49 +++--- src/error.rs | 11 +- src/fn_call.rs | 288 ++++++++++++++++++++------------- src/fn_native.rs | 2 +- src/optimize.rs | 4 +- src/parser.rs | 66 +++++--- src/scope.rs | 13 +- src/token.rs | 14 +- tests/functions.rs | 52 +++++- 17 files changed, 410 insertions(+), 180 deletions(-) create mode 100644 doc/src/language/fn-capture.md diff --git a/RELEASES.md b/RELEASES.md index 7f59cddf..f163f06f 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -9,6 +9,8 @@ This version adds: * Binding the `this` pointer in a function pointer `call`. * Anonymous functions (in Rust closure syntax). Simplifies creation of single-use ad-hoc functions. * Currying of function pointers. +* Auto-currying of anonymous functions. +* Capturing call scope via `func!(...)` syntax. New features ------------ @@ -19,6 +21,8 @@ New features * Anonymous functions are supported in the syntax of a Rust closure, e.g. `|x, y, z| x + y - z`. * Custom syntax now works even without the `internals` feature. * Currying of function pointers is supported via the new `curry` keyword. +* Automatic currying of anonymous functions to capture environment variables. +* Capturing of the calling scope for function call via the `func!(...)` syntax. * `Module::set_indexer_get_set_fn` is added as a shorthand of both `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn`. * New `unicode-xid-ident` feature to allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) for identifiers. diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index 78ea9882..6b966de1 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -100,21 +100,22 @@ The Rhai Scripting Language 8. [Maximum Call Stack Depth](safety/max-call-stack.md) 9. [Maximum Statement Depth](safety/max-stmt-depth.md) 7. [Advanced Topics](advanced.md) - 1. [Object-Oriented Programming (OOP)](language/oop.md) - 2. [Serialization/Deserialization of `Dynamic` with `serde`](rust/serde.md) - 3. [Script Optimization](engine/optimize/index.md) + 1. [Capture Scope for Function Call](language/fn-capture.md) + 2. [Object-Oriented Programming (OOP)](language/oop.md) + 3. [Serialization/Deserialization of `Dynamic` with `serde`](rust/serde.md) + 4. [Script Optimization](engine/optimize/index.md) 1. [Optimization Levels](engine/optimize/optimize-levels.md) 2. [Re-Optimize an AST](engine/optimize/reoptimize.md) 3. [Eager Function Evaluation](engine/optimize/eager.md) 4. [Side-Effect Considerations](engine/optimize/side-effects.md) 5. [Volatility Considerations](engine/optimize/volatility.md) 6. [Subtle Semantic Changes](engine/optimize/semantics.md) - 4. [Low-Level API](rust/register-raw.md) - 5. [Use as DSL](engine/dsl.md) + 5. [Low-Level API](rust/register-raw.md) + 6. [Use as DSL](engine/dsl.md) 1. [Disable Keywords and/or Operators](engine/disable.md) 2. [Custom Operators](engine/custom-op.md) 3. [Extending with Custom Syntax](engine/custom-syntax.md) - 6. [Eval Statement](language/eval.md) + 7. [Eval Statement](language/eval.md) 8. [Appendix](appendix/index.md) 1. [Keywords](appendix/keywords.md) 2. [Operators and Symbols](appendix/operators.md) diff --git a/doc/src/advanced.md b/doc/src/advanced.md index cd958c73..6b79fe70 100644 --- a/doc/src/advanced.md +++ b/doc/src/advanced.md @@ -5,6 +5,8 @@ Advanced Topics This section covers advanced features such as: +* [Capture the calling scope]({{rootUrl}}/language/fn-capture.md) in a function call. + * Simulated [Object Oriented Programming (OOP)][OOP]. * [`serde`] integration. diff --git a/doc/src/language/fn-anon.md b/doc/src/language/fn-anon.md index a4cb0fee..e76ba59c 100644 --- a/doc/src/language/fn-anon.md +++ b/doc/src/language/fn-anon.md @@ -54,7 +54,7 @@ WARNING - NOT Closures ---------------------- Remember: anonymous functions, though having the same syntax as Rust _closures_, are themselves -**not** closures. In particular, they do not capture their running environment. They are more like +**not** closures. In particular, they do not capture their execution environment. They are more like Rust's function pointers. They do, however, _capture_ variable _values_ from their execution environment, unless the [`no_capture`] diff --git a/doc/src/language/fn-capture.md b/doc/src/language/fn-capture.md new file mode 100644 index 00000000..042675e9 --- /dev/null +++ b/doc/src/language/fn-capture.md @@ -0,0 +1,62 @@ +Capture The Calling Scope for Function Call +========================================== + +{{#include ../links.md}} + + +Peeking Out of The Pure Box +--------------------------- + +Rhai functions are _pure_, meaning that they depend on on their arguments and have no +access to the calling environment. + +When a function accesses a variable that is not defined within that function's scope, +it raises an evaluation error. + +It is possible, through a special syntax, to capture the calling scope - i.e. the scope +that makes the function call - and access variables defined there. + +```rust +fn foo(y) { // function accesses 'x' and 'y', but 'x' is not defined + x += y; // 'x' is modified in this function + x +} + +let x = 1; + +foo(41); // error: variable 'x' not found + +// Calling a function with a '!' causes it to capture the calling scope + +foo!(41) == 42; // the function can access the value of 'x', but cannot change it + +x == 1; // 'x' is still the original value + +x.method!(); // <- syntax error: capturing is not allowed in method-call style + +// Capturing also works for function pointers + +let f = Fn("foo"); + +call!(f, 41) == 42; // must use function-call style + +f.call!(41); // <- syntax error: capturing is not allowed in method-call style +``` + + +No Mutations +------------ + +Variables in the calling scope are accessed as copies. +Changes to them do not reflect back to the calling scope. + +Rhai functions remain _pure_ in the sense that they can never mutate their environment. + + +Caveat Emptor +------------- + +Functions relying on the calling scope is a _Very Bad Idea™_ because it makes code almost impossible +to reason and maintain, as their behaviors are volatile and unpredictable. + +This usage should be at the last resort. diff --git a/doc/src/language/fn-closure.md b/doc/src/language/fn-closure.md index d8749601..1701cc4a 100644 --- a/doc/src/language/fn-closure.md +++ b/doc/src/language/fn-closure.md @@ -1,6 +1,8 @@ Capture External Variables via Automatic Currying ================================================ +{{#include ../links.md}} + Poor Man's Closures ------------------- diff --git a/doc/src/language/fn-curry.md b/doc/src/language/fn-curry.md index 71c8933a..83705175 100644 --- a/doc/src/language/fn-curry.md +++ b/doc/src/language/fn-curry.md @@ -33,7 +33,7 @@ curried.call(2) == 42; // <- de-sugars to 'func.call(21, 2)' Automatic Currying ------------------ -[Anonymous functions] defined via a closure syntax _capture_ external variables that are not shadowed inside -the function's scope. +[Anonymous functions] defined via a closure syntax _capture_ the _values_ of external variables +that are not shadowed inside the function's scope. This is accomplished via [automatic currying]. diff --git a/doc/src/start/features.md b/doc/src/start/features.md index 7a06618a..efeb39c5 100644 --- a/doc/src/start/features.md +++ b/doc/src/start/features.md @@ -23,7 +23,7 @@ more control over what a script can (or cannot) do. | `no_object` | Disable support for [custom types] and [object maps]. | | `no_function` | Disable script-defined [functions]. | | `no_module` | Disable loading external [modules]. | -| `no_capture` | Disable capturing external variables in [anonymous functions]. | +| `no_capture` | Disable [capturing][capture] external variables in [anonymous functions] and [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls. | | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | | `serde` | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. | | `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. | diff --git a/src/engine.rs b/src/engine.rs index 2ad03365..ac264af4 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -686,7 +686,7 @@ impl Engine { let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val]; self.exec_fn_call( - state, lib, FN_IDX_SET, true, 0, args, is_ref, true, false, + state, lib, FN_IDX_SET, 0, args, is_ref, true, false, None, None, level, ) .or_else(|err| match *err { @@ -710,7 +710,7 @@ impl Engine { let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val]; self.exec_fn_call( - state, lib, FN_IDX_SET, true, 0, args, is_ref, true, false, + state, lib, FN_IDX_SET, 0, args, is_ref, true, false, None, None, level, )?; } @@ -732,7 +732,7 @@ impl Engine { match rhs { // xxx.fn_name(arg_expr_list) Expr::FnCall(x) if x.1.is_none() => { - let ((name, native, pos), _, hash, _, def_val) = x.as_ref(); + let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref(); self.make_method_call( state, lib, name, *hash, target, idx_val, *def_val, *native, false, level, @@ -766,7 +766,7 @@ impl Engine { let ((_, _, setter), pos) = x.as_ref(); let mut args = [target.as_mut(), _new_val.as_mut().unwrap()]; self.exec_fn_call( - state, lib, setter, true, 0, &mut args, is_ref, true, false, None, + state, lib, setter, 0, &mut args, is_ref, true, false, None, None, level, ) .map(|(v, _)| (v, true)) @@ -777,7 +777,7 @@ impl Engine { let ((_, getter, _), pos) = x.as_ref(); let mut args = [target.as_mut()]; self.exec_fn_call( - state, lib, getter, true, 0, &mut args, is_ref, true, false, None, + state, lib, getter, 0, &mut args, is_ref, true, false, None, None, level, ) .map(|(v, _)| (v, false)) @@ -795,7 +795,7 @@ impl Engine { } // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr Expr::FnCall(x) if x.1.is_none() => { - let ((name, native, pos), _, hash, _, def_val) = x.as_ref(); + let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref(); let (val, _) = self .make_method_call( state, lib, name, *hash, target, idx_val, *def_val, @@ -828,7 +828,7 @@ impl Engine { let args = &mut arg_values[..1]; let (mut val, updated) = self .exec_fn_call( - state, lib, getter, true, 0, args, is_ref, true, false, + state, lib, getter, 0, args, is_ref, true, false, None, None, level, ) .map_err(|err| err.new_position(*pos))?; @@ -848,8 +848,8 @@ impl Engine { // Re-use args because the first &mut parameter will not be consumed arg_values[1] = val; self.exec_fn_call( - state, lib, setter, true, 0, arg_values, is_ref, true, - false, None, level, + state, lib, setter, 0, arg_values, is_ref, true, false, + None, None, level, ) .or_else( |err| match *err { @@ -866,7 +866,7 @@ impl Engine { } // xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr Expr::FnCall(x) if x.1.is_none() => { - let ((name, native, pos), _, hash, _, def_val) = x.as_ref(); + let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref(); let (mut val, _) = self .make_method_call( state, lib, name, *hash, target, idx_val, *def_val, @@ -1138,7 +1138,7 @@ impl Engine { let type_name = val.type_name(); let args = &mut [val, &mut _idx]; self.exec_fn_call( - state, _lib, FN_IDX_GET, true, 0, args, is_ref, true, false, None, _level, + state, _lib, FN_IDX_GET, 0, args, is_ref, true, false, None, None, _level, ) .map(|(v, _)| v.into()) .map_err(|err| match *err { @@ -1186,15 +1186,13 @@ impl Engine { let def_value = Some(false); let args = &mut [&mut lhs_value.clone(), value]; - let hashes = ( - // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. - calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id())), - 0, - ); + // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. + let hash = + calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id())); let (r, _) = self .call_fn_raw( - &mut scope, mods, state, lib, op, hashes, args, false, false, false, + &mut scope, mods, state, lib, op, hash, args, false, false, false, def_value, level, ) .map_err(|err| err.new_position(rhs.position()))?; @@ -1301,14 +1299,14 @@ impl Engine { } else if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_none() { // Not built in, map to `var = var op rhs` let op = &op[..op.len() - 1]; // extract operator without = - let hash = calc_fn_hash(empty(), op, 2, empty()); + // Clone the LHS value let args = &mut [&mut lhs_ptr.clone(), &mut rhs_val]; + // Run function let (value, _) = self .exec_fn_call( - state, lib, op, true, hash, args, false, false, false, None, - level, + state, lib, op, 0, args, false, false, false, None, None, level, ) .map_err(|err| err.new_position(*op_pos))?; // Set value to LHS @@ -1331,13 +1329,12 @@ impl Engine { } else { // Op-assignment - always map to `lhs = lhs op rhs` let op = &op[..op.len() - 1]; // extract operator without = - let hash = calc_fn_hash(empty(), op, 2, empty()); let args = &mut [ &mut self.eval_expr(scope, mods, state, lib, this_ptr, lhs_expr, level)?, &mut rhs_val, ]; self.exec_fn_call( - state, lib, op, true, hash, args, false, false, false, None, level, + state, lib, op, 0, args, false, false, false, None, None, level, ) .map(|(v, _)| v) .map_err(|err| err.new_position(*op_pos))? @@ -1407,20 +1404,20 @@ impl Engine { // Normal function call Expr::FnCall(x) if x.1.is_none() => { - let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref(); + let ((name, native, capture, pos), _, hash, args_expr, def_val) = x.as_ref(); self.make_function_call( scope, mods, state, lib, this_ptr, name, args_expr, *def_val, *hash, *native, - false, level, + false, *capture, level, ) .map_err(|err| err.new_position(*pos)) } // Module-qualified function call Expr::FnCall(x) if x.1.is_some() => { - let ((name, _, pos), modules, hash, args_expr, def_val) = x.as_ref(); + let ((name, _, capture, pos), modules, hash, args_expr, def_val) = x.as_ref(); self.make_qualified_function_call( scope, mods, state, lib, this_ptr, modules, name, args_expr, *def_val, *hash, - level, + *capture, level, ) .map_err(|err| err.new_position(*pos)) } diff --git a/src/error.rs b/src/error.rs index fc92e5ea..259d6916 100644 --- a/src/error.rs +++ b/src/error.rs @@ -91,6 +91,10 @@ pub enum ParseErrorType { /// /// Never appears under the `no_object` and `no_index` features combination. MalformedInExpr(String), + /// A capturing has syntax error. Wrapped value is the error description (if any). + /// + /// Never appears under the `no_capture` feature. + MalformedCapture(String), /// A map definition has duplicated property names. Wrapped value is the property name. /// /// Never appears under the `no_object` feature. @@ -166,6 +170,7 @@ impl ParseErrorType { Self::MalformedCallExpr(_) => "Invalid expression in function call arguments", Self::MalformedIndexExpr(_) => "Invalid index in indexing expression", Self::MalformedInExpr(_) => "Invalid 'in' expression", + Self::MalformedCapture(_) => "Invalid capturing", Self::DuplicatedProperty(_) => "Duplicated property in object map literal", Self::ForbiddenConstantExpr(_) => "Expecting a constant", Self::PropertyExpected => "Expecting name of a property", @@ -199,9 +204,9 @@ impl fmt::Display for ParseErrorType { } Self::UnknownOperator(s) => write!(f, "{}: '{}'", self.desc(), s), - Self::MalformedIndexExpr(s) => f.write_str(if s.is_empty() { self.desc() } else { s }), - - Self::MalformedInExpr(s) => f.write_str(if s.is_empty() { self.desc() } else { s }), + Self::MalformedIndexExpr(s) | Self::MalformedInExpr(s) | Self::MalformedCapture(s) => { + f.write_str(if s.is_empty() { self.desc() } else { s }) + } Self::DuplicatedProperty(s) => { write!(f, "Duplicated property '{}' for object map literal", s) diff --git a/src/fn_call.rs b/src/fn_call.rs index 861f490b..ca1aef51 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -19,8 +19,9 @@ use crate::utils::StaticVec; #[cfg(not(feature = "no_function"))] use crate::{ - parser::ScriptFnDef, r#unsafe::unsafe_cast_var_name_to_lifetime, - scope::EntryType as ScopeEntryType, + parser::ScriptFnDef, + r#unsafe::unsafe_cast_var_name_to_lifetime, + scope::{Entry as ScopeEntry, EntryType as ScopeEntryType}, }; #[cfg(not(feature = "no_float"))] @@ -105,8 +106,32 @@ fn restore_first_arg<'a>(old_this_ptr: Option<&'a mut Dynamic>, args: &mut FnCal } } +// Add captured variables into scope +#[cfg(not(feature = "no_capture"))] +fn add_captured_variables_into_scope<'s>( + externals: &[String], + captured: &'s Scope<'s>, + scope: &mut Scope<'s>, +) { + externals + .iter() + .map(|var_name| captured.get_entry(var_name)) + .filter(Option::is_some) + .map(Option::unwrap) + .for_each( + |ScopeEntry { + name, typ, value, .. + }| { + match typ { + ScopeEntryType::Normal => scope.push(name.clone(), value.clone()), + ScopeEntryType::Constant => scope.push_constant(name.clone(), value.clone()), + }; + }, + ); +} + impl Engine { - /// Universal method for calling functions either registered with the `Engine` or written in Rhai. + /// Call a native Rust function registered with the `Engine`. /// Position in `EvalAltResult` is `None` and must be set afterwards. /// /// ## WARNING @@ -121,7 +146,7 @@ impl Engine { state: &mut State, lib: &Module, fn_name: &str, - (hash_fn, hash_script): (u64, u64), + hash_fn: u64, args: &mut FnCallArgs, is_ref: bool, _is_method: bool, @@ -131,8 +156,6 @@ impl Engine { ) -> Result<(Dynamic, bool), Box> { self.inc_operations(state)?; - let native_only = hash_script == 0; - // Check for stack overflow #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "unchecked"))] @@ -142,23 +165,13 @@ impl Engine { )); } - let mut this_copy: Dynamic = Default::default(); - let mut old_this_ptr: Option<&mut Dynamic> = None; - // Search for the function - // First search in script-defined functions (can override built-in) - // Then search registered native functions (can override packages) + // First search registered native functions (can override packages) // Then search packages - // NOTE: We skip script functions for global_module and packages, and native functions for lib - let func = if !native_only { - lib.get_fn(hash_script, pub_only) //.or_else(|| lib.get_fn(hash_fn, pub_only)) - } else { - None - } - //.or_else(|| self.global_module.get_fn(hash_script, pub_only)) - .or_else(|| self.global_module.get_fn(hash_fn, pub_only)) - //.or_else(|| self.packages.get_fn(hash_script, pub_only)) - .or_else(|| self.packages.get_fn(hash_fn, pub_only)); + let func = self + .global_module + .get_fn(hash_fn, pub_only) + .or_else(|| self.packages.get_fn(hash_fn, pub_only)); if let Some(func) = func { #[cfg(not(feature = "no_function"))] @@ -166,43 +179,12 @@ impl Engine { #[cfg(feature = "no_function")] let need_normalize = is_ref && func.is_pure(); + let mut this_copy: Dynamic = Default::default(); + let mut old_this_ptr: Option<&mut Dynamic> = None; + // Calling pure function but the first argument is a reference? normalize_first_arg(need_normalize, &mut this_copy, &mut old_this_ptr, args); - #[cfg(not(feature = "no_function"))] - if func.is_script() { - // Run scripted function - let fn_def = func.get_fn_def(); - - // Method call of script function - map first argument to `this` - return if _is_method { - let (first, rest) = args.split_at_mut(1); - Ok(( - self.call_script_fn( - _scope, - _mods, - state, - lib, - &mut Some(first[0]), - fn_name, - fn_def, - rest, - _level, - )?, - false, - )) - } else { - let result = self.call_script_fn( - _scope, _mods, state, lib, &mut None, fn_name, fn_def, args, _level, - )?; - - // Restore the original reference - restore_first_arg(old_this_ptr, args); - - Ok((result, false)) - }; - } - // Run external function let result = func.get_native_fn()(self, lib, args)?; @@ -414,24 +396,23 @@ impl Engine { state: &mut State, lib: &Module, fn_name: &str, - native_only: bool, hash_script: u64, args: &mut FnCallArgs, is_ref: bool, is_method: bool, pub_only: bool, + capture: Option, def_val: Option, level: usize, ) -> Result<(Dynamic, bool), Box> { // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. let arg_types = args.iter().map(|a| a.type_id()); let hash_fn = calc_fn_hash(empty(), fn_name, args.len(), arg_types); - let hashes = (hash_fn, if native_only { 0 } else { hash_script }); match fn_name { // type_of KEYWORD_TYPE_OF - if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1, pub_only) => + if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) => { Ok(( self.map_type_name(args[0].type_name()).to_string().into(), @@ -441,7 +422,7 @@ impl Engine { // Fn KEYWORD_FN_PTR - if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1, pub_only) => + if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) => { Err(Box::new(EvalAltResult::ErrorRuntime( "'Fn' should not be called in method style. Try Fn(...);".into(), @@ -451,7 +432,7 @@ impl Engine { // eval - reaching this point it must be a method-style call KEYWORD_EVAL - if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1, pub_only) => + if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) => { Err(Box::new(EvalAltResult::ErrorRuntime( "'eval' should not be called in method style. Try eval(...);".into(), @@ -459,12 +440,61 @@ impl Engine { ))) } - // Normal function call + // Normal script function call + #[cfg(not(feature = "no_function"))] + _ if hash_script > 0 && lib.contains_fn(hash_script, pub_only) => { + // Get scripted function + let func = lib.get_fn(hash_script, pub_only).unwrap().get_fn_def(); + + let scope = &mut Scope::new(); + let mods = &mut Imports::new(); + + // Add captured variables into scope + #[cfg(not(feature = "no_capture"))] + if let Some(captured) = &capture { + add_captured_variables_into_scope(&func.externals, captured, scope); + } + + let result = if is_method { + // Method call of script function - map first argument to `this` + let (first, rest) = args.split_at_mut(1); + self.call_script_fn( + scope, + mods, + state, + lib, + &mut Some(first[0]), + fn_name, + func, + rest, + level, + )? + } else { + // Normal call of script function - map first argument to `this` + let mut first_copy: Dynamic = Default::default(); + let mut old_first: Option<&mut Dynamic> = None; + + // The first argument is a reference? + normalize_first_arg(is_ref, &mut first_copy, &mut old_first, args); + + let result = self.call_script_fn( + scope, mods, state, lib, &mut None, fn_name, func, args, level, + )?; + + // Restore the original reference + restore_first_arg(old_first, args); + + result + }; + + Ok((result, false)) + } + // Normal native function call _ => { let mut scope = Scope::new(); let mut mods = Imports::new(); self.call_fn_raw( - &mut scope, &mut mods, state, lib, fn_name, hashes, args, is_ref, is_method, + &mut scope, &mut mods, state, lib, fn_name, hash_fn, args, is_ref, is_method, pub_only, def_val, level, ) } @@ -522,7 +552,7 @@ impl Engine { state: &mut State, lib: &Module, name: &str, - hash: u64, + hash_script: u64, target: &mut Target, idx_val: Dynamic, def_val: Option, @@ -545,7 +575,11 @@ impl Engine { // Redirect function name let fn_name = fn_ptr.fn_name(); // Recalculate hash - let hash = calc_fn_hash(empty(), fn_name, curry.len() + idx.len(), empty()); + let hash = if native { + 0 + } else { + calc_fn_hash(empty(), fn_name, curry.len() + idx.len(), empty()) + }; // Arguments are passed as-is, adding the curried arguments let mut arg_values = curry .iter_mut() @@ -555,7 +589,7 @@ impl Engine { // Map it to name(args) in function-call style self.exec_fn_call( - state, lib, fn_name, native, hash, args, false, false, pub_only, def_val, level, + state, lib, fn_name, hash, args, false, false, pub_only, None, def_val, level, ) } else if _fn_name == KEYWORD_FN_PTR_CALL && idx.len() > 0 && idx[0].is::() { // FnPtr call on object @@ -564,7 +598,11 @@ impl Engine { // Redirect function name let fn_name = fn_ptr.get_fn_name().clone(); // Recalculate hash - let hash = calc_fn_hash(empty(), &fn_name, curry.len() + idx.len(), empty()); + let hash = if native { + 0 + } else { + calc_fn_hash(empty(), &fn_name, curry.len() + idx.len(), empty()) + }; // Replace the first argument with the object pointer, adding the curried arguments let mut arg_values = once(obj) .chain(curry.iter_mut()) @@ -574,7 +612,7 @@ impl Engine { // Map it to name(args) in function-call style self.exec_fn_call( - state, lib, &fn_name, native, hash, args, is_ref, true, pub_only, def_val, level, + state, lib, &fn_name, hash, args, is_ref, true, pub_only, None, def_val, level, ) } else if _fn_name == KEYWORD_FN_PTR_CURRY && obj.is::() { // Curry call @@ -595,7 +633,7 @@ impl Engine { } else { #[cfg(not(feature = "no_object"))] let redirected; - let mut _hash = hash; + let mut _hash = hash_script; // Check if it is a map method call in OOP style #[cfg(not(feature = "no_object"))] @@ -611,12 +649,16 @@ impl Engine { } }; + if native { + _hash = 0; + } + // Attached object pointer in front of the arguments let mut arg_values = once(obj).chain(idx.iter_mut()).collect::>(); let args = arg_values.as_mut(); self.exec_fn_call( - state, lib, _fn_name, native, _hash, args, is_ref, true, pub_only, def_val, level, + state, lib, _fn_name, _hash, args, is_ref, true, pub_only, None, def_val, level, ) }?; @@ -641,16 +683,17 @@ impl Engine { name: &str, args_expr: &[Expr], def_val: Option, - mut hash: u64, + mut hash_script: u64, native: bool, pub_only: bool, + capture: bool, level: usize, ) -> Result> { // Handle Fn() if name == KEYWORD_FN_PTR && args_expr.len() == 1 { let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::())); - if !self.has_override(lib, hash_fn, hash, pub_only) { + if !self.has_override(lib, hash_fn, hash_script, pub_only) { // Fn - only in function call style let expr = args_expr.get(0).unwrap(); let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; @@ -698,11 +741,43 @@ impl Engine { .into()); } + // Handle call() - Redirect function call + let redirected; + let mut args_expr = args_expr.as_ref(); + let mut curry: StaticVec<_> = Default::default(); + let mut name = name; + + if name == KEYWORD_FN_PTR_CALL + && args_expr.len() >= 1 + && !self.has_override(lib, 0, hash_script, pub_only) + { + let expr = args_expr.get(0).unwrap(); + let fn_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + + if fn_name.is::() { + let fn_ptr = fn_name.cast::(); + curry = fn_ptr.curry().iter().cloned().collect(); + // Redirect function name + redirected = fn_ptr.take_data().0; + name = &redirected; + // Skip the first argument + args_expr = &args_expr.as_ref()[1..]; + // Recalculate hash + hash_script = calc_fn_hash(empty(), name, curry.len() + args_expr.len(), empty()); + } else { + return Err(Box::new(EvalAltResult::ErrorMismatchOutputType( + self.map_type_name(type_name::()).into(), + fn_name.type_name().into(), + expr.position(), + ))); + } + } + // Handle eval() if name == KEYWORD_EVAL && args_expr.len() == 1 { let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::())); - if !self.has_override(lib, hash_fn, hash, pub_only) { + if !self.has_override(lib, hash_fn, hash_script, pub_only) { // eval - only in function call style let prev_len = scope.len(); let expr = args_expr.get(0).unwrap(); @@ -721,42 +796,15 @@ impl Engine { } } - // Handle call() - Redirect function call - let redirected; - let mut args_expr = args_expr.as_ref(); - let mut curry: StaticVec<_> = Default::default(); - let mut name = name; - - if name == KEYWORD_FN_PTR_CALL - && args_expr.len() >= 1 - && !self.has_override(lib, 0, hash, pub_only) - { - let expr = args_expr.get(0).unwrap(); - let fn_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; - - if fn_name.is::() { - let fn_ptr = fn_name.cast::(); - curry = fn_ptr.curry().iter().cloned().collect(); - // Redirect function name - redirected = fn_ptr.take_data().0; - name = &redirected; - // Skip the first argument - args_expr = &args_expr.as_ref()[1..]; - // Recalculate hash - hash = calc_fn_hash(empty(), name, curry.len() + args_expr.len(), empty()); - } else { - return Err(Box::new(EvalAltResult::ErrorMismatchOutputType( - self.map_type_name(type_name::()).into(), - fn_name.type_name().into(), - expr.position(), - ))); - } - } - - // Normal function call - except for Fn and eval (handled above) + // Normal function call - except for Fn, curry, call and eval (handled above) let mut arg_values: StaticVec<_>; let mut args: StaticVec<_>; let mut is_ref = false; + let capture = if capture && !scope.is_empty() { + Some(scope.clone()) + } else { + None + }; if args_expr.is_empty() && curry.is_empty() { // No arguments @@ -797,9 +845,11 @@ impl Engine { } } + let hash = if native { 0 } else { hash_script }; let args = args.as_mut(); + self.exec_fn_call( - state, lib, name, native, hash, args, is_ref, false, pub_only, def_val, level, + state, lib, name, hash, args, is_ref, false, pub_only, capture, def_val, level, ) .map(|(v, _)| v) } @@ -818,10 +868,18 @@ impl Engine { args_expr: &[Expr], def_val: Option, hash_script: u64, + capture: bool, level: usize, ) -> Result> { let modules = modules.as_ref().unwrap(); + #[cfg(not(feature = "no_capture"))] + let capture = if capture && !scope.is_empty() { + Some(scope.clone()) + } else { + None + }; + let mut arg_values: StaticVec<_>; let mut args: StaticVec<_>; @@ -887,12 +945,18 @@ impl Engine { #[cfg(not(feature = "no_function"))] Some(f) if f.is_script() => { let args = args.as_mut(); - let fn_def = f.get_fn_def(); - let mut scope = Scope::new(); - let mut mods = Imports::new(); - self.call_script_fn( - &mut scope, &mut mods, state, lib, &mut None, name, fn_def, args, level, - ) + let func = f.get_fn_def(); + + let scope = &mut Scope::new(); + let mods = &mut Imports::new(); + + // Add captured variables into scope + #[cfg(not(feature = "no_capture"))] + if let Some(captured) = &capture { + add_captured_variables_into_scope(&func.externals, captured, scope); + } + + self.call_script_fn(scope, mods, state, lib, &mut None, name, func, args, level) } Some(f) => f.get_native_fn()(self, lib, args.as_mut()), None if def_val.is_some() => Ok(def_val.unwrap().into()), diff --git a/src/fn_native.rs b/src/fn_native.rs index 5751351c..f6bb962e 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -130,13 +130,13 @@ impl FnPtr { &mut Default::default(), lib.as_ref(), fn_name, - false, hash_script, args.as_mut(), has_this, has_this, true, None, + None, 0, ) .map(|(v, _)| v) diff --git a/src/optimize.rs b/src/optimize.rs index e26d1908..7c71aced 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -138,7 +138,7 @@ fn call_fn_with_constant_arguments( &mut Default::default(), state.lib, fn_name, - (hash_fn, 0), + hash_fn, arg_values.iter_mut().collect::>().as_mut(), false, false, @@ -576,7 +576,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { && state.optimization_level == OptimizationLevel::Full // full optimizations && x.3.iter().all(|expr| expr.is_constant()) // all arguments are constants => { - let ((name, _, pos), _, _, args, def_value) = x.as_mut(); + let ((name, _, _, pos), _, _, args, def_value) = x.as_mut(); // First search in functions lib (can override built-in) // Cater for both normal function call style and method call style (one additional arguments) diff --git a/src/parser.rs b/src/parser.rs index abe3c7d8..c8f4e314 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -748,12 +748,12 @@ pub enum Expr { Stmt(Box<(Stmt, Position)>), /// Wrapped expression - should not be optimized away. Expr(Box), - /// func(expr, ... ) - ((function name, native_only, position), optional modules, hash, arguments, optional default value) + /// func(expr, ... ) - ((function name, native_only, capture, position), optional modules, hash, arguments, optional default value) /// Use `Cow<'static, str>` because a lot of operators (e.g. `==`, `>=`) are implemented as function calls /// and the function names are predictable, so no need to allocate a new `String`. FnCall( Box<( - (Cow<'static, str>, bool, Position), + (Cow<'static, str>, bool, bool, Position), Option>, u64, StaticVec, @@ -871,7 +871,7 @@ impl Expr { Self::Property(x) => x.1, Self::Stmt(x) => x.1, Self::Variable(x) => (x.0).1, - Self::FnCall(x) => (x.0).2, + Self::FnCall(x) => (x.0).3, Self::Assignment(x) => x.0.position(), Self::And(x) | Self::Or(x) | Self::In(x) => x.2, @@ -903,7 +903,7 @@ impl Expr { Self::Variable(x) => (x.0).1 = new_pos, Self::Property(x) => x.1 = new_pos, Self::Stmt(x) => x.1 = new_pos, - Self::FnCall(x) => (x.0).2 = new_pos, + Self::FnCall(x) => (x.0).3 = new_pos, Self::And(x) => x.2 = new_pos, Self::Or(x) => x.2 = new_pos, Self::In(x) => x.2 = new_pos, @@ -1009,6 +1009,7 @@ impl Expr { #[cfg(not(feature = "no_index"))] Token::LeftBracket => true, Token::LeftParen => true, + Token::Bang => true, Token::DoubleColon => true, _ => false, }, @@ -1101,6 +1102,7 @@ fn parse_fn_call( state: &mut ParseState, lib: &mut FunctionsLib, id: String, + capture: bool, mut modules: Option>, settings: ParseSettings, ) -> Result { @@ -1143,7 +1145,7 @@ fn parse_fn_call( }; return Ok(Expr::FnCall(Box::new(( - (id.into(), false, settings.pos), + (id.into(), false, capture, settings.pos), modules, hash_script, args, @@ -1185,7 +1187,7 @@ fn parse_fn_call( }; return Ok(Expr::FnCall(Box::new(( - (id.into(), false, settings.pos), + (id.into(), false, capture, settings.pos), modules, hash_script, args, @@ -1594,6 +1596,8 @@ fn parse_primary( _ => input.next().unwrap(), }; + let (next_token, _) = input.peek().unwrap(); + let mut root_expr = match token { Token::IntegerConstant(x) => Expr::IntegerConstant(Box::new((x, settings.pos))), #[cfg(not(feature = "no_float"))] @@ -1605,7 +1609,7 @@ fn parse_primary( Expr::Variable(Box::new(((s, settings.pos), None, 0, index))) } // Function call is allowed to have reserved keyword - Token::Reserved(s) if input.peek().unwrap().0 == Token::LeftParen => { + Token::Reserved(s) if *next_token == Token::LeftParen || *next_token == Token::Bang => { if is_keyword_function(&s) { Expr::Variable(Box::new(((s, settings.pos), None, 0, None))) } else { @@ -1613,7 +1617,7 @@ fn parse_primary( } } // Access to `this` as a variable is OK - Token::Reserved(s) if s == KEYWORD_THIS && input.peek().unwrap().0 != Token::LeftParen => { + Token::Reserved(s) if s == KEYWORD_THIS && *next_token != Token::LeftParen => { if !settings.is_function_scope { return Err( PERR::BadInput(format!("'{}' can only be used in functions", s)) @@ -1653,11 +1657,26 @@ fn parse_primary( settings.pos = token_pos; root_expr = match (root_expr, token) { + // Function call + #[cfg(not(feature = "no_capture"))] + (Expr::Variable(x), Token::Bang) => { + if !match_token(input, Token::LeftParen)? { + return Err(PERR::MissingToken( + Token::LeftParen.syntax().into(), + "to start arguments list of function call".into(), + ) + .into_err(input.peek().unwrap().1)); + } + + let ((name, pos), modules, _, _) = *x; + settings.pos = pos; + parse_fn_call(input, state, lib, name, true, modules, settings.level_up())? + } // Function call (Expr::Variable(x), Token::LeftParen) => { let ((name, pos), modules, _, _) = *x; settings.pos = pos; - parse_fn_call(input, state, lib, name, modules, settings.level_up())? + parse_fn_call(input, state, lib, name, false, modules, settings.level_up())? } (Expr::Property(_), _) => unreachable!(), // module access @@ -1767,7 +1786,7 @@ fn parse_unary( args.push(expr); Ok(Expr::FnCall(Box::new(( - (op.into(), true, pos), + (op.into(), true, false, pos), None, hash, args, @@ -1792,7 +1811,7 @@ fn parse_unary( let hash = calc_fn_hash(empty(), op, 2, empty()); Ok(Expr::FnCall(Box::new(( - (op.into(), true, pos), + (op.into(), true, false, pos), None, hash, args, @@ -1987,7 +2006,14 @@ fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result { + return Err(PERR::MalformedCapture( + "method-call style does not support capturing".into(), + ) + .into_err((x.0).3)) + } + // lhs.func(...) (lhs, func @ Expr::FnCall(_)) => Expr::Dot(Box::new((lhs, func, op_pos))), // lhs.rhs (_, rhs) => return Err(PERR::PropertyExpected.into_err(rhs.position())), @@ -2196,7 +2222,7 @@ fn parse_binary_op( let cmp_def = Some(false); let op = op_token.syntax(); let hash = calc_fn_hash(empty(), &op, 2, empty()); - let op = (op, true, pos); + let op = (op, true, false, pos); let mut args = StaticVec::new(); args.push(root); @@ -2257,7 +2283,7 @@ fn parse_binary_op( .unwrap_or(false) => { // Accept non-native functions for custom operators - let op = (op.0, false, op.2); + let op = (op.0, false, op.2, op.3); Expr::FnCall(Box::new((op, None, hash, args, None))) } @@ -2975,10 +3001,12 @@ fn parse_fn( let (token, pos) = input.next().unwrap(); - let name = token.into_function_name().map_err(|t| match t { - Token::Reserved(s) => PERR::Reserved(s).into_err(pos), - _ => PERR::FnMissingName.into_err(pos), - })?; + let name = token + .into_function_name_for_override() + .map_err(|t| match t { + Token::Reserved(s) => PERR::Reserved(s).into_err(pos), + _ => PERR::FnMissingName.into_err(pos), + })?; match input.peek().unwrap() { (Token::LeftParen, _) => eat_token(input, Token::LeftParen), @@ -3085,7 +3113,7 @@ fn make_curry_from_externals( let hash = calc_fn_hash(empty(), KEYWORD_FN_PTR_CURRY, num_externals, empty()); let fn_call = Expr::FnCall(Box::new(( - (KEYWORD_FN_PTR_CURRY.into(), false, pos), + (KEYWORD_FN_PTR_CURRY.into(), false, false, pos), None, hash, args, diff --git a/src/scope.rs b/src/scope.rs index 5693554c..3e43b850 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -316,6 +316,14 @@ impl<'a> Scope<'a> { }) } + /// Get an entry in the Scope, starting from the last. + pub(crate) fn get_entry(&self, name: &str) -> Option<&Entry> { + self.0 + .iter() + .rev() + .find(|Entry { name: key, .. }| name == key) + } + /// Get the value of an entry in the Scope, starting from the last. /// /// # Examples @@ -329,10 +337,7 @@ impl<'a> Scope<'a> { /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` pub fn get_value(&self, name: &str) -> Option { - self.0 - .iter() - .rev() - .find(|Entry { name: key, .. }| name == key) + self.get_entry(name) .and_then(|Entry { value, .. }| value.downcast_ref::().cloned()) } diff --git a/src/token.rs b/src/token.rs index 7d1a07ba..a35ff4f6 100644 --- a/src/token.rs +++ b/src/token.rs @@ -678,9 +678,9 @@ impl Token { } /// Convert a token into a function name, if possible. - pub(crate) fn into_function_name(self) -> Result { + pub(crate) fn into_function_name_for_override(self) -> Result { match self { - Self::Reserved(s) if is_keyword_function(&s) => Ok(s), + Self::Reserved(s) if can_override_keyword(&s) => Ok(s), Self::Custom(s) | Self::Identifier(s) if is_valid_identifier(s.chars()) => Ok(s), _ => Err(self), } @@ -1439,6 +1439,16 @@ pub fn is_keyword_function(name: &str) -> bool { || name == KEYWORD_FN_PTR_CURRY } +/// Can this keyword be overridden as a function? +#[inline(always)] +pub fn can_override_keyword(name: &str) -> bool { + name == KEYWORD_PRINT + || name == KEYWORD_DEBUG + || name == KEYWORD_TYPE_OF + || name == KEYWORD_EVAL + || name == KEYWORD_FN_PTR +} + pub fn is_valid_identifier(name: impl Iterator) -> bool { let mut first_alphabetic = false; diff --git a/tests/functions.rs b/tests/functions.rs index 78d20fa3..9336cb6d 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -1,5 +1,5 @@ #![cfg(not(feature = "no_function"))] -use rhai::{Engine, EvalAltResult, INT}; +use rhai::{Engine, EvalAltResult, ParseError, ParseErrorType, INT}; #[test] fn test_functions() -> Result<(), Box> { @@ -120,3 +120,53 @@ fn test_function_pointers() -> Result<(), Box> { Ok(()) } + +#[test] +#[cfg(not(feature = "no_capture"))] +fn test_function_captures() -> Result<(), Box> { + let engine = Engine::new(); + + assert_eq!( + engine.eval::( + r#" + fn foo(y) { x += y; x } + + let x = 41; + let y = 999; + + foo!(1) + x + "# + )?, + 83 + ); + + assert!(engine + .eval::( + r#" + fn foo(y) { x += y; x } + + let x = 41; + let y = 999; + + foo(1) + x + "# + ) + .is_err()); + + #[cfg(not(feature = "no_object"))] + assert!(matches!( + engine.compile( + r#" + fn foo() { this += x; } + + let x = 41; + let y = 999; + + y.foo!(); + "# + ).expect_err("should error"), + ParseError(err, _) if matches!(*err, ParseErrorType::MalformedCapture(_)) + )); + + Ok(()) +} From 7d4620d0d9cf4f8ff1349dc08bd5b476121f80c8 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 30 Jul 2020 23:29:11 +0800 Subject: [PATCH 26/64] Unbounded -> unbound. --- doc/src/language/functions.md | 2 +- src/engine.rs | 4 ++-- src/result.rs | 12 ++++++------ tests/fn_ptr.rs | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/doc/src/language/functions.md b/doc/src/language/functions.md index d8f67502..3bd15cd0 100644 --- a/doc/src/language/functions.md +++ b/doc/src/language/functions.md @@ -127,5 +127,5 @@ x.change(); // call 'change' in method-call style, 'this' binds to 'x' x == 42; // 'x' is changed! -change(); // <- error: `this` is unbounded +change(); // <- error: `this` is unbound ``` diff --git a/src/engine.rs b/src/engine.rs index ac264af4..6aa1f9c2 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -556,7 +556,7 @@ pub fn search_scope_only<'s, 'a>( if let Some(val) = this_ptr { return Ok(((*val).into(), KEYWORD_THIS, ScopeEntryType::Normal, *pos)); } else { - return Err(Box::new(EvalAltResult::ErrorUnboundedThis(*pos))); + return Err(Box::new(EvalAltResult::ErrorUnboundThis(*pos))); } } @@ -1247,7 +1247,7 @@ impl Engine { if let Some(val) = this_ptr { Ok(val.clone()) } else { - Err(Box::new(EvalAltResult::ErrorUnboundedThis((x.0).1))) + Err(Box::new(EvalAltResult::ErrorUnboundThis((x.0).1))) } } Expr::Variable(_) => { diff --git a/src/result.rs b/src/result.rs index 550dcc37..c0cc620d 100644 --- a/src/result.rs +++ b/src/result.rs @@ -39,8 +39,8 @@ pub enum EvalAltResult { /// An error has occurred inside a called function. /// Wrapped values are the name of the function and the interior error. ErrorInFunctionCall(String, Box, Position), - /// Access to `this` that is not bounded. - ErrorUnboundedThis(Position), + /// Access to `this` that is not bound. + ErrorUnboundThis(Position), /// Non-boolean operand encountered for boolean operator. Wrapped value is the operator. ErrorBooleanArgMismatch(String, Position), /// Non-character value encountered where a character is required. @@ -112,7 +112,7 @@ impl EvalAltResult { Self::ErrorParsing(p, _) => p.desc(), Self::ErrorInFunctionCall(_, _, _) => "Error in called function", Self::ErrorFunctionNotFound(_, _) => "Function not found", - Self::ErrorUnboundedThis(_) => "'this' is not bounded", + Self::ErrorUnboundThis(_) => "'this' is not bound", Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands", Self::ErrorCharMismatch(_) => "Character expected", Self::ErrorNumericIndexExpr(_) => { @@ -187,7 +187,7 @@ impl fmt::Display for EvalAltResult { Self::ErrorIndexingType(_, _) | Self::ErrorNumericIndexExpr(_) | Self::ErrorStringIndexExpr(_) - | Self::ErrorUnboundedThis(_) + | Self::ErrorUnboundThis(_) | Self::ErrorImportExpr(_) | Self::ErrorLogicGuard(_) | Self::ErrorFor(_) @@ -276,7 +276,7 @@ impl EvalAltResult { Self::ErrorParsing(_, pos) | Self::ErrorFunctionNotFound(_, pos) | Self::ErrorInFunctionCall(_, _, pos) - | Self::ErrorUnboundedThis(pos) + | Self::ErrorUnboundThis(pos) | Self::ErrorBooleanArgMismatch(_, pos) | Self::ErrorCharMismatch(pos) | Self::ErrorArrayBounds(_, _, pos) @@ -316,7 +316,7 @@ impl EvalAltResult { Self::ErrorParsing(_, pos) | Self::ErrorFunctionNotFound(_, pos) | Self::ErrorInFunctionCall(_, _, pos) - | Self::ErrorUnboundedThis(pos) + | Self::ErrorUnboundThis(pos) | Self::ErrorBooleanArgMismatch(_, pos) | Self::ErrorCharMismatch(pos) | Self::ErrorArrayBounds(_, _, pos) diff --git a/tests/fn_ptr.rs b/tests/fn_ptr.rs index 833bb20c..c6658f17 100644 --- a/tests/fn_ptr.rs +++ b/tests/fn_ptr.rs @@ -73,7 +73,7 @@ fn test_fn_ptr() -> Result<(), Box> { "# ) .expect_err("should error"), - EvalAltResult::ErrorInFunctionCall(fn_name, err, _) if fn_name == "foo" && matches!(*err, EvalAltResult::ErrorUnboundedThis(_)) + EvalAltResult::ErrorInFunctionCall(fn_name, err, _) if fn_name == "foo" && matches!(*err, EvalAltResult::ErrorUnboundThis(_)) )); Ok(()) From a7ff20763659e055568e54d70a8a9ca4440c936d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 30 Jul 2020 23:29:30 +0800 Subject: [PATCH 27/64] Use Scope::flatten_clone for capturing. --- src/fn_call.rs | 25 ++++++++++++------------- src/parser.rs | 6 +++--- src/scope.rs | 21 +++++++++++++++++++-- 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/fn_call.rs b/src/fn_call.rs index ca1aef51..6672d746 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -36,6 +36,7 @@ use crate::engine::{Map, Target, FN_GET, FN_SET}; use crate::stdlib::{ any::{type_name, TypeId}, boxed::Box, + collections::HashSet, convert::TryFrom, format, iter::{empty, once}, @@ -109,22 +110,20 @@ fn restore_first_arg<'a>(old_this_ptr: Option<&'a mut Dynamic>, args: &mut FnCal // Add captured variables into scope #[cfg(not(feature = "no_capture"))] fn add_captured_variables_into_scope<'s>( - externals: &[String], - captured: &'s Scope<'s>, + externals: &HashSet, + captured: Scope<'s>, scope: &mut Scope<'s>, ) { - externals - .iter() - .map(|var_name| captured.get_entry(var_name)) - .filter(Option::is_some) - .map(Option::unwrap) + captured + .into_iter() + .filter(|ScopeEntry { name, .. }| externals.contains(name.as_ref())) .for_each( |ScopeEntry { name, typ, value, .. }| { match typ { - ScopeEntryType::Normal => scope.push(name.clone(), value.clone()), - ScopeEntryType::Constant => scope.push_constant(name.clone(), value.clone()), + ScopeEntryType::Normal => scope.push(name, value), + ScopeEntryType::Constant => scope.push_constant(name, value), }; }, ); @@ -451,7 +450,7 @@ impl Engine { // Add captured variables into scope #[cfg(not(feature = "no_capture"))] - if let Some(captured) = &capture { + if let Some(captured) = capture { add_captured_variables_into_scope(&func.externals, captured, scope); } @@ -801,7 +800,7 @@ impl Engine { let mut args: StaticVec<_>; let mut is_ref = false; let capture = if capture && !scope.is_empty() { - Some(scope.clone()) + Some(scope.flatten_clone()) } else { None }; @@ -875,7 +874,7 @@ impl Engine { #[cfg(not(feature = "no_capture"))] let capture = if capture && !scope.is_empty() { - Some(scope.clone()) + Some(scope.flatten_clone()) } else { None }; @@ -952,7 +951,7 @@ impl Engine { // Add captured variables into scope #[cfg(not(feature = "no_capture"))] - if let Some(captured) = &capture { + if let Some(captured) = capture { add_captured_variables_into_scope(&func.externals, captured, scope); } diff --git a/src/parser.rs b/src/parser.rs index c8f4e314..3fd6365b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -25,7 +25,7 @@ use crate::stdlib::{ borrow::Cow, boxed::Box, char, - collections::HashMap, + collections::{HashMap, HashSet}, fmt, format, hash::{Hash, Hasher}, iter::empty, @@ -355,7 +355,7 @@ impl fmt::Display for FnAccess { /// ## WARNING /// /// This type is volatile and may change. -#[derive(Debug, Clone, Hash)] +#[derive(Debug, Clone)] pub struct ScriptFnDef { /// Function name. pub name: ImmutableString, @@ -365,7 +365,7 @@ pub struct ScriptFnDef { pub params: StaticVec, /// Access to external variables. #[cfg(not(feature = "no_capture"))] - pub externals: StaticVec, + pub externals: HashSet, /// Function body. pub body: Stmt, /// Position of the function definition. diff --git a/src/scope.rs b/src/scope.rs index 3e43b850..b2024564 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -4,7 +4,9 @@ use crate::any::{Dynamic, Variant}; use crate::parser::{map_dynamic_to_expr, Expr}; use crate::token::Position; -use crate::stdlib::{borrow::Cow, boxed::Box, iter, string::String, vec::Vec}; +use crate::stdlib::{ + borrow::Cow, boxed::Box, collections::HashMap, iter, string::String, vec::Vec, +}; /// Type of an entry in the Scope. #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] @@ -389,13 +391,28 @@ impl<'a> Scope<'a> { self } + /// Clone the Scope, keeping only the last instances of each variable name. + /// Shadowed variables are omitted in the copy. + #[cfg(not(feature = "no_capture"))] + pub(crate) fn flatten_clone(&self) -> Self { + let mut entries: HashMap<&str, Entry> = Default::default(); + + self.0.iter().rev().for_each(|entry| { + entries + .entry(entry.name.as_ref()) + .or_insert_with(|| entry.clone()); + }); + + Self(entries.into_iter().map(|(_, v)| v).collect()) + } + /// Get an iterator to entries in the Scope. #[cfg(not(feature = "no_module"))] pub(crate) fn into_iter(self) -> impl Iterator> { self.0.into_iter() } - /// Get an iterator to entries in the Scope. + /// Get an iterator to entries in the Scope in reverse order. pub(crate) fn to_iter(&self) -> impl Iterator { self.0.iter().rev() // Always search a Scope in reverse order } From e5fe222de3a61146ae34c4b85b6be3c91cbcf717 Mon Sep 17 00:00:00 2001 From: Ilya Lakhin Date: Mon, 27 Jul 2020 11:30:09 +0700 Subject: [PATCH 28/64] Shared variant of Dynamic type; All read/write access operations in Dynamic backed by Read/Write lock guards; new shared() script function --- Cargo.toml | 1 + src/any.rs | 287 ++++++++++++++++++++++++++++++++++-- src/engine.rs | 39 ++++- src/fn_call.rs | 36 +++-- src/fn_native.rs | 13 +- src/fn_register.rs | 46 +++--- src/module.rs | 20 +-- src/packages/array_basic.rs | 4 +- src/packages/string_more.rs | 4 +- src/scope.rs | 2 +- src/token.rs | 4 +- 11 files changed, 383 insertions(+), 73 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ec00a10c..85b57974 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ no_index = [] # no arrays and indexing no_object = [] # no custom objects no_function = [] # no script-defined functions no_capture = [] # no automatic read/write binding of anonymous function's local variables to it's external context +no_shared = [] # no explicit shared variables in the script code no_module = [] # no modules internals = [] # expose internal data structures unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. diff --git a/src/any.rs b/src/any.rs index 9d28c768..a7983030 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1,6 +1,6 @@ //! Helper module which defines the `Any` trait to to allow dynamic value handling. -use crate::fn_native::{FnPtr, SendSync}; +use crate::fn_native::{FnPtr, SendSync, SharedMut}; use crate::parser::{ImmutableString, INT}; use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast}; @@ -18,8 +18,15 @@ use crate::stdlib::{ boxed::Box, fmt, string::String, + ops::{Deref, DerefMut} }; +#[cfg(not(feature = "sync"))] +use crate::stdlib::{rc::Rc, cell::{RefCell, Ref, RefMut}}; + +#[cfg(feature = "sync")] +use crate::stdlib::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; + #[cfg(not(feature = "no_object"))] use crate::stdlib::collections::HashMap; @@ -144,6 +151,88 @@ pub enum Union { Map(Box), FnPtr(Box), Variant(Box>), + Shared(Box), +} + +/// Internal Shared Dynamic representation. +/// +/// Created with `Dynamic::into_shared()`. +#[derive(Clone)] +pub struct SharedCell { + value_type_id: TypeId, + value_type_name: &'static str, + container: SharedMut +} + +/// Dynamic's underlying `Variant` read guard that supports `Deref` for Variant's +/// reference reading. +/// +/// This data structure provides transparent interoperability between normal +/// `Dynamic` types and Shared Dynamic references. +#[derive(Debug)] +pub struct DynamicReadLock<'d, T: Variant + Clone>(DynamicReadLockInner<'d, T>); + +#[derive(Debug)] +enum DynamicReadLockInner<'d, T: Variant + Clone> { + Reference(&'d T), + #[cfg(not(feature = "sync"))] + Guard(Ref<'d, Dynamic>), + #[cfg(feature = "sync")] + Guard(RwLockReadGuard<'d, Dynamic>), +} + +impl<'d, T: Variant + Clone> Deref for DynamicReadLock<'d, T> { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + match &self.0 { + DynamicReadLockInner::Reference(reference) => reference.deref(), + // unwrapping is safe because all checks already done in it's constructor + DynamicReadLockInner::Guard(guard) => guard.downcast_ref().unwrap(), + } + } +} + +/// Dynamic's underlying `Variant` write guard that supports `Deref` and `DerefMut` +/// for Variant's reference reading/writing. +/// +/// This data structure provides transparent interoperability between normal +/// `Dynamic` types and Shared Dynamic references. +#[derive(Debug)] +pub struct DynamicWriteLock<'d, T: Variant + Clone>(DynamicWriteLockInner<'d, T>); + +#[derive(Debug)] +enum DynamicWriteLockInner<'d, T: Variant + Clone> { + Reference(&'d mut T), + #[cfg(not(feature = "sync"))] + Guard(RefMut<'d, Dynamic>), + #[cfg(feature = "sync")] + Guard(RwLockWriteGuard<'d, Dynamic>), +} + +impl<'d, T: Variant + Clone> Deref for DynamicWriteLock<'d, T> { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + match &self.0 { + DynamicWriteLockInner::Reference(reference) => reference.deref(), + // unwrapping is safe because all checks already done in it's constructor + DynamicWriteLockInner::Guard(guard) => guard.downcast_ref().unwrap(), + } + } +} + +impl<'d, T: Variant + Clone> DerefMut for DynamicWriteLock<'d, T> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + match &mut self.0 { + DynamicWriteLockInner::Reference(reference) => reference.deref_mut(), + // unwrapping is safe because all checks already done in it's constructor + DynamicWriteLockInner::Guard(guard) => guard.downcast_mut().unwrap(), + } + } } impl Dynamic { @@ -156,7 +245,19 @@ impl Dynamic { } } + /// Does this `Dynamic` hold a shared data type + /// instead of one of the support system primitive types? + pub fn is_shared(&self) -> bool { + match self.0 { + Union::Shared(_) => true, + _ => false, + } + } + /// Is the value held by this `Dynamic` a particular type? + /// + /// If the `Dynamic` is a Shared variant checking is performed on + /// top of it's internal value. pub fn is(&self) -> bool { self.type_id() == TypeId::of::() || match self.0 { @@ -181,6 +282,7 @@ impl Dynamic { Union::Map(_) => TypeId::of::(), Union::FnPtr(_) => TypeId::of::(), Union::Variant(value) => (***value).type_id(), + Union::Shared(cell) => (**cell).value_type_id, } } @@ -203,6 +305,7 @@ impl Dynamic { #[cfg(not(feature = "no_std"))] Union::Variant(value) if value.is::() => "timestamp", Union::Variant(value) => (***value).type_name(), + Union::Shared(cell) => (**cell).value_type_name, } } } @@ -258,6 +361,7 @@ impl fmt::Display for Dynamic { #[cfg(not(feature = "no_std"))] Union::Variant(value) if value.is::() => write!(f, ""), Union::Variant(value) => write!(f, "{}", (*value).type_name()), + Union::Shared(cell) => write!(f, "{}", (**cell).value_type_name), } } } @@ -284,6 +388,7 @@ impl fmt::Debug for Dynamic { #[cfg(not(feature = "no_std"))] Union::Variant(value) if value.is::() => write!(f, ""), Union::Variant(value) => write!(f, "{}", (*value).type_name()), + Union::Shared(cell) => write!(f, "{}", (**cell).value_type_name), } } } @@ -304,6 +409,7 @@ impl Clone for Dynamic { Union::Map(ref value) => Self(Union::Map(value.clone())), Union::FnPtr(ref value) => Self(Union::FnPtr(value.clone())), Union::Variant(ref value) => (***value).clone_into_dynamic(), + Union::Shared(ref cell) => Self(Union::Shared(Box::new((**cell).clone()))) } } } @@ -415,10 +521,53 @@ impl Dynamic { Self(Union::Variant(Box::new(boxed))) } + /// Turns Dynamic into Shared Dynamic variant backed by runtime + /// mutable reference-counter container(`Arc>` or + /// `Rc` depending on `sync` feature). + /// + /// Instances of Shared Dynamic are relatively cheap to clone. All clones of + /// Shared Dynamic references the same chunk of memory. + /// + /// The Dynamic is capable to work transparently with the it's inner + /// data, seamlessly casting between ordinary instances of Dynamic and + /// Shared Dynamic instances. + /// + /// If original value already a Shared variant returns identity. + pub fn into_shared(self) -> Self { + match self.0 { + Union::Shared(..) => self, + _ => { + let cell = SharedCell { + value_type_id: self.type_id(), + value_type_name: self.type_name(), + #[cfg(not(feature = "sync"))] + container: Rc::new(RefCell::new(self)), + #[cfg(feature = "sync")] + container: Arc::new(RwLock::new(self)), + }; + + Self(Union::Shared(Box::new(cell))) + }, + } + } + /// Get a copy of the `Dynamic` value as a specific type. /// Casting to a `Dynamic` just returns as is. /// - /// Returns an error with the name of the value's actual type when the cast fails. + /// Returns None if types mismatched. + /// + /// # Shared Dynamic + /// + /// When accessing Shared Dynamic in sync mode(`sync` feature enabled) + /// can block current thread while the underlined data is being written. + /// + /// When accessing Shared Dynamic in NON-sync mode can **panic** if the data + /// is currently borrowed for write. + /// + /// ## Safety + /// + /// Both situations normally shouldn't happen since all operations in Rhai + /// use pass-by-value data and the script executed in a single thread. /// /// # Example /// @@ -433,6 +582,24 @@ impl Dynamic { pub fn try_cast(self) -> Option { let type_id = TypeId::of::(); + if type_id == TypeId::of::() { + return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v); + } + + #[cfg(not(feature = "sync"))] + if let Union::Shared(cell) = self.0 { + let reference = cell.container.borrow(); + + return (*reference).clone().try_cast() + } + + #[cfg(feature = "sync")] + if let Union::Shared(cell) = self.0 { + let read_lock = cell.container.read().unwrap(); + + return (*read_lock).clone().try_cast() + } + if type_id == TypeId::of::() { return match self.0 { Union::Int(value) => unsafe_try_cast(value), @@ -496,9 +663,6 @@ impl Dynamic { _ => None, }; } - if type_id == TypeId::of::() { - return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v); - } match self.0 { Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(), @@ -511,7 +675,14 @@ impl Dynamic { /// /// # Panics /// - /// Panics if the cast fails (e.g. the type of the actual value is not the same as the specified type). + /// Panics if the cast fails (e.g. the type of the actual value is not the + /// same as the specified type), or if the data held by Shared Dynamic is + /// currently borrowed(when the `sync` feature disabled). + /// + /// # Notes + /// + /// If the `sync` feature enabled Shared Dynamic can block current thread + /// while the data being written. /// /// # Example /// @@ -531,7 +702,104 @@ impl Dynamic { /// Casting to `Dynamic` just returns a reference to it. /// Returns `None` if the cast fails. #[inline(always)] - pub fn downcast_ref(&self) -> Option<&T> { + pub fn read_lock(&self) -> Option> { + match self.0 { + Union::Shared(ref cell) => { + let type_id = TypeId::of::(); + + if type_id != TypeId::of::() && cell.value_type_id != type_id { + return None + } + + #[cfg(not(feature = "sync"))] + return Some(DynamicReadLock(DynamicReadLockInner::Guard( + cell.container.borrow() + ))); + + #[cfg(feature = "sync")] + return Some(DynamicReadLock(DynamicReadLockInner::Guard( + cell.container.read().unwrap() + ))); + }, + _ => { + self.downcast_ref().map(|reference| { + DynamicReadLock(DynamicReadLockInner::Reference(reference)) + }) + } + } + } + + /// Get a copy of a specific type to the `Dynamic`. + /// Casting to `Dynamic` just returns a reference to it. + /// Returns `None` if the cast fails. + #[inline(always)] + pub fn read(&self) -> Option { + match self.0 { + Union::Shared(ref cell) => { + let type_id = TypeId::of::(); + + if type_id != TypeId::of::() && cell.value_type_id != type_id { + return None + } + + #[cfg(not(feature = "sync"))] + return Some(cell + .container + .borrow() + .deref() + .downcast_ref::() + .unwrap() + .clone()); + + #[cfg(feature = "sync")] + return Some(cell + .container + .read() + .unwrap() + .deref() + .downcast_ref::() + .unwrap() + .clone()); + }, + _ => { + self.downcast_ref().cloned() + } + } + } + + /// Get a mutable reference of a specific type to the `Dynamic`. + /// Casting to `Dynamic` just returns a mutable reference to it. + /// Returns `None` if the cast fails. + #[inline(always)] + pub fn write_lock(&mut self) -> Option> { + match self.0 { + Union::Shared(ref cell) => { + let type_id = TypeId::of::(); + + if type_id != TypeId::of::() && cell.value_type_id != type_id { + return None + } + + #[cfg(not(feature = "sync"))] + return Some(DynamicWriteLock(DynamicWriteLockInner::Guard( + cell.container.borrow_mut() + ))); + + #[cfg(feature = "sync")] + return Some(DynamicWriteLock(DynamicWriteLockInner::Guard( + cell.container.write().unwrap() + ))); + }, + _ => { + self.downcast_mut().map(|reference| { + DynamicWriteLock(DynamicWriteLockInner::Reference(reference)) + }) + } + } + } + + #[inline(always)] + fn downcast_ref(&self) -> Option<&T> { let type_id = TypeId::of::(); if type_id == TypeId::of::() { @@ -607,11 +875,8 @@ impl Dynamic { } } - /// Get a mutable reference of a specific type to the `Dynamic`. - /// Casting to `Dynamic` just returns a mutable reference to it. - /// Returns `None` if the cast fails. #[inline(always)] - pub fn downcast_mut(&mut self) -> Option<&mut T> { + fn downcast_mut(&mut self) -> Option<&mut T> { let type_id = TypeId::of::(); if type_id == TypeId::of::() { diff --git a/src/engine.rs b/src/engine.rs index 7806382f..06ff8d7f 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -91,6 +91,7 @@ pub const KEYWORD_EVAL: &str = "eval"; pub const KEYWORD_FN_PTR: &str = "Fn"; pub const KEYWORD_FN_PTR_CALL: &str = "call"; pub const KEYWORD_FN_PTR_CURRY: &str = "curry"; +pub const KEYWORD_SHARED: &str = "shared"; pub const KEYWORD_THIS: &str = "this"; pub const FN_TO_STRING: &str = "to_string"; #[cfg(not(feature = "no_object"))] @@ -1068,6 +1069,18 @@ impl Engine { let val = target.as_mut(); + // if val.is_shared() { + // return self.get_indexed_mut( + // state, + // lib, + // &mut Target::Value(val.read::().unwrap()), + // idx, + // idx_pos, + // create, + // level, + // ); + // } + match val { #[cfg(not(feature = "no_index"))] Dynamic(Union::Array(arr)) => { @@ -1102,7 +1115,7 @@ impl Engine { map.entry(index).or_insert(Default::default()).into() } else { let index = _idx - .downcast_ref::() + .read_lock::() .ok_or_else(|| EvalAltResult::ErrorStringIndexExpr(idx_pos))?; map.get_mut(index.as_str()) @@ -1280,7 +1293,11 @@ impl Engine { )), // Normal assignment ScopeEntryType::Normal if op.is_empty() => { - *lhs_ptr = rhs_val; + if lhs_ptr.is_shared() { + *lhs_ptr.write_lock::().unwrap() = rhs_val; + } else { + *lhs_ptr = rhs_val; + } Ok(Default::default()) } // Op-assignment - in order of precedence: @@ -1298,8 +1315,13 @@ impl Engine { .get_fn(hash_fn, false) .or_else(|| self.packages.get_fn(hash_fn, false)) { - // Overriding exact implementation - func(self, lib, &mut [lhs_ptr, &mut rhs_val])?; + if lhs_ptr.is_shared() { + // Overriding exact implementation + func(self, lib, &mut [&mut lhs_ptr.write_lock::().unwrap(), &mut rhs_val])?; + } else { + // Overriding exact implementation + func(self, lib, &mut [lhs_ptr, &mut rhs_val])?; + } } else if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_none() { // Not built in, map to `var = var op rhs` let op = &op[..op.len() - 1]; // extract operator without = @@ -1313,8 +1335,13 @@ impl Engine { level, ) .map_err(|err| err.new_position(*op_pos))?; - // Set value to LHS - *lhs_ptr = value; + if lhs_ptr.is_shared() { + // Set value to LHS + *lhs_ptr.write_lock::().unwrap() = value; + } else { + // Set value to LHS + *lhs_ptr = value; + } } Ok(Default::default()) } diff --git a/src/fn_call.rs b/src/fn_call.rs index 162410f3..6ca46046 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -5,7 +5,7 @@ use crate::calc_fn_hash; use crate::engine::{ search_imports, search_namespace, search_scope_only, Engine, Imports, State, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_PRINT, - KEYWORD_TYPE_OF, + KEYWORD_TYPE_OF, KEYWORD_SHARED }; use crate::error::ParseErrorType; use crate::fn_native::{FnCallArgs, FnPtr}; @@ -16,6 +16,7 @@ use crate::result::EvalAltResult; use crate::scope::Scope; use crate::token::Position; use crate::utils::StaticVec; +use crate::stdlib::ops::Deref; #[cfg(not(feature = "no_function"))] use crate::{ @@ -540,7 +541,7 @@ impl Engine { let (result, updated) = if _fn_name == KEYWORD_FN_PTR_CALL && obj.is::() { // FnPtr call - let fn_ptr = obj.downcast_ref::().unwrap(); + let fn_ptr = obj.read_lock::().unwrap(); let mut curry = fn_ptr.curry().iter().cloned().collect::>(); // Redirect function name let fn_name = fn_ptr.fn_name(); @@ -578,7 +579,7 @@ impl Engine { ) } else if _fn_name == KEYWORD_FN_PTR_CURRY && obj.is::() { // Curry call - let fn_ptr = obj.downcast_ref::().unwrap(); + let fn_ptr = obj.read_lock::().unwrap(); Ok(( FnPtr::new_unchecked( fn_ptr.get_fn_name().clone(), @@ -599,9 +600,9 @@ impl Engine { // Check if it is a map method call in OOP style #[cfg(not(feature = "no_object"))] - if let Some(map) = obj.downcast_ref::() { + if let Some(map) = obj.read_lock::() { if let Some(val) = map.get(_fn_name) { - if let Some(f) = val.downcast_ref::() { + if let Some(f) = val.read_lock::() { // Remap the function name redirected = f.get_fn_name().clone(); _fn_name = &redirected; @@ -697,6 +698,15 @@ impl Engine { .into()); } + // Handle shared() + #[cfg(not(feature = "no_shared"))] + if name == KEYWORD_SHARED && args_expr.len() == 1 { + let expr = args_expr.get(0).unwrap(); + let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + + return Ok(value.into_shared()); + } + // Handle eval() if name == KEYWORD_EVAL && args_expr.len() == 1 { let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::())); @@ -984,8 +994,8 @@ pub fn run_builtin_binary_op( _ => (), } } else if args_type == TypeId::of::() { - let x = x.downcast_ref::().unwrap(); - let y = y.downcast_ref::().unwrap(); + let x = &*x.read_lock::().unwrap(); + let y = &*y.read_lock::().unwrap(); match op { "+" => return Ok(Some((x + y).into())), @@ -1058,7 +1068,7 @@ pub fn run_builtin_op_assignment( } if args_type == TypeId::of::() { - let x = x.downcast_mut::().unwrap(); + let mut x = x.write_lock::().unwrap(); let y = y.clone().cast::(); #[cfg(not(feature = "unchecked"))] @@ -1094,7 +1104,7 @@ pub fn run_builtin_op_assignment( _ => (), } } else if args_type == TypeId::of::() { - let x = x.downcast_mut::().unwrap(); + let mut x = x.write_lock::().unwrap(); let y = y.clone().cast::(); match op { @@ -1103,18 +1113,18 @@ pub fn run_builtin_op_assignment( _ => (), } } else if args_type == TypeId::of::() { - let x = x.downcast_mut::().unwrap(); - let y = y.downcast_ref::().unwrap(); + let mut x = x.write_lock::().unwrap(); + let y = y.read_lock::().unwrap(); match op { - "+=" => return Ok(Some(*x += y)), + "+=" => return Ok(Some(*x += y.deref())), _ => (), } } #[cfg(not(feature = "no_float"))] if args_type == TypeId::of::() { - let x = x.downcast_mut::().unwrap(); + let mut x = x.write_lock::().unwrap(); let y = y.clone().cast::(); match op { diff --git a/src/fn_native.rs b/src/fn_native.rs index 5751351c..e51bf71b 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -1,6 +1,6 @@ //! Module defining interfaces to native-Rust functions. -use crate::any::Dynamic; +use crate::any::{Dynamic, Variant}; use crate::calc_fn_hash; use crate::engine::Engine; use crate::module::Module; @@ -18,9 +18,9 @@ use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, iter::empty, string::Stri use crate::stdlib::mem; #[cfg(not(feature = "sync"))] -use crate::stdlib::rc::Rc; +use crate::stdlib::{rc::Rc, cell::{RefCell, Ref, RefMut}}; #[cfg(feature = "sync")] -use crate::stdlib::sync::Arc; +use crate::stdlib::sync::{Arc, RwLock}; /// Trait that maps to `Send + Sync` only under the `sync` feature. #[cfg(feature = "sync")] @@ -34,11 +34,18 @@ pub trait SendSync {} #[cfg(not(feature = "sync"))] impl SendSync for T {} +/// Immutable reference counting container #[cfg(not(feature = "sync"))] pub type Shared = Rc; #[cfg(feature = "sync")] pub type Shared = Arc; +/// Mutable reference counting container(read-write lock) +#[cfg(not(feature = "sync"))] +pub type SharedMut = Rc>; +#[cfg(feature = "sync")] +pub type SharedMut = Arc>; + /// Consume a `Shared` resource and return a mutable reference to the wrapped value. /// If the resource is shared (i.e. has other outstanding references), a cloned copy is used. pub fn shared_make_mut(value: &mut Shared) -> &mut T { diff --git a/src/fn_register.rs b/src/fn_register.rs index c2eb5534..ddcddff3 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -2,7 +2,7 @@ #![allow(non_snake_case)] -use crate::any::{Dynamic, Variant}; +use crate::any::{Dynamic, Variant, DynamicWriteLock}; use crate::engine::Engine; use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync}; use crate::module::Module; @@ -97,11 +97,11 @@ pub trait RegisterResultFn { pub struct Mut(T); //pub struct Ref(T); -/// Dereference into &mut. +/// Dereference into DynamicWriteLock #[inline(always)] -pub fn by_ref(data: &mut Dynamic) -> &mut T { - // Directly cast the &mut Dynamic into &mut T to access the underlying data. - data.downcast_mut::().unwrap() +pub fn by_ref(data: &mut Dynamic) -> DynamicWriteLock { + // Directly cast the &mut Dynamic into DynamicWriteLock to access the underlying data. + data.write_lock::().unwrap() } /// Dereference into value. @@ -124,24 +124,23 @@ pub fn by_value(data: &mut Dynamic) -> T { /// This macro creates a closure wrapping a registered function. macro_rules! make_func { - ($fn:ident : $map:expr ; $($par:ident => $convert:expr),*) => { + ($fn:ident : $map:expr ; $($par:ident => $let:stmt => $convert:expr => $arg:expr),*) => { // ^ function pointer // ^ result mapping function // ^ function parameter generic type name (A, B, C etc.) -// ^ dereferencing function +// ^ argument let statement(e.g. let mut A ...) +// ^ dereferencing function +// ^ argument reference expression(like A, *B, &mut C etc) Box::new(move |_: &Engine, _: &Module, args: &mut FnCallArgs| { // The arguments are assumed to be of the correct number and types! let mut _drain = args.iter_mut(); - $( - // Downcast every element, panic in case of a type mismatch (which shouldn't happen). - // Call the user-supplied function using ($convert) to access it either by value or by reference. - let $par = ($convert)(_drain.next().unwrap()); - )* + $($let)* + $($par = ($convert)(_drain.next().unwrap()); )* // Call the function with each parameter value - let r = $fn($($par),*); + let r = $fn($($arg),*); // Map the result $map(r) @@ -181,12 +180,13 @@ macro_rules! def_register { () => { def_register!(imp from_pure :); }; - (imp $abi:ident : $($par:ident => $mark:ty => $param:ty => $clone:expr),*) => { + (imp $abi:ident : $($par:ident => $arg:expr => $mark:ty => $param:ty => $let:stmt => $clone:expr),*) => { // ^ function ABI type // ^ function parameter generic type name (A, B, C etc.) - // ^ function parameter marker type (T, Ref or Mut) - // ^ function parameter actual type (T, &T or &mut T) - // ^ dereferencing function +// ^ call argument(like A, *B, &mut C etc) + // ^ function parameter marker type (T, Ref or Mut) + // ^ function parameter actual type (T, &T or &mut T) + // ^ argument let statement impl< $($par: Variant + Clone,)* FN: Fn($($param),*) -> RET + SendSync + 'static, @@ -196,7 +196,7 @@ macro_rules! def_register { fn register_fn(&mut self, name: &str, f: FN) -> &mut Self { self.global_module.set_fn(name, FnAccess::Public, &[$(map_type_id::<$par>()),*], - CallableFunction::$abi(make_func!(f : map_dynamic ; $($par => $clone),*)) + CallableFunction::$abi(make_func!(f : map_dynamic ; $($par => $let => $clone => $arg),*)) ); self } @@ -210,7 +210,7 @@ macro_rules! def_register { fn register_result_fn(&mut self, name: &str, f: FN) -> &mut Self { self.global_module.set_fn(name, FnAccess::Public, &[$(map_type_id::<$par>()),*], - CallableFunction::$abi(make_func!(f : map_result ; $($par => $clone),*)) + CallableFunction::$abi(make_func!(f : map_result ; $($par => $let => $clone => $arg),*)) ); self } @@ -219,11 +219,11 @@ macro_rules! def_register { //def_register!(imp_pop $($par => $mark => $param),*); }; ($p0:ident $(, $p:ident)*) => { - def_register!(imp from_pure : $p0 => $p0 => $p0 => by_value $(, $p => $p => $p => by_value)*); - def_register!(imp from_method : $p0 => Mut<$p0> => &mut $p0 => by_ref $(, $p => $p => $p => by_value)*); + def_register!(imp from_pure : $p0 => $p0 => $p0 => $p0 => let $p0 => by_value $(, $p => $p => $p => $p => let $p => by_value)*); + def_register!(imp from_method : $p0 => &mut $p0 => Mut<$p0> => &mut $p0 => let mut $p0 => by_ref $(, $p => $p => $p => $p => let $p => by_value)*); // ^ CallableFunction - // handle the first parameter ^ first parameter passed through - // ^ others passed by value (by_value) + // handle the first parameter ^ first parameter passed through + // ^ others passed by value (by_value) // Currently does not support first argument which is a reference, as there will be // conflicting implementations since &T: Any and T: Any cannot be distinguished diff --git a/src/module.rs b/src/module.rs index 8eacde7b..fa23abd6 100644 --- a/src/module.rs +++ b/src/module.rs @@ -442,7 +442,7 @@ impl Module { /// // Since it is a primary type, it can also be cheaply copied /// let double = args[1].clone().cast::(); /// // Get a mutable reference to the first argument. - /// let x = args[0].downcast_mut::().unwrap(); + /// let mut x = args[0].write_lock::().unwrap(); /// /// let orig = *x; /// @@ -534,7 +534,7 @@ impl Module { func: impl Fn(&mut A) -> FuncReturn + SendSync + 'static, ) -> u64 { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { - func(args[0].downcast_mut::
().unwrap()).map(Dynamic::from) + func(&mut args[0].write_lock::().unwrap()).map(Dynamic::from) }; let arg_types = [TypeId::of::()]; self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f))) @@ -615,9 +615,9 @@ impl Module { ) -> u64 { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let b = mem::take(args[1]).cast::(); - let a = args[0].downcast_mut::().unwrap(); + let mut a = args[0].write_lock::().unwrap(); - func(a, b).map(Dynamic::from) + func(&mut a, b).map(Dynamic::from) }; let arg_types = [TypeId::of::(), TypeId::of::()]; self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f))) @@ -739,9 +739,9 @@ impl Module { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); - let a = args[0].downcast_mut::().unwrap(); + let mut a = args[0].write_lock::().unwrap(); - func(a, b, c).map(Dynamic::from) + func(&mut a, b, c).map(Dynamic::from) }; let arg_types = [TypeId::of::(), TypeId::of::(), TypeId::of::()]; self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f))) @@ -773,9 +773,9 @@ impl Module { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); - let a = args[0].downcast_mut::().unwrap(); + let mut a = args[0].write_lock::().unwrap(); - func(a, b, c).map(Dynamic::from) + func(&mut a, b, c).map(Dynamic::from) }; let arg_types = [TypeId::of::(), TypeId::of::(), TypeId::of::()]; self.set_fn( @@ -896,9 +896,9 @@ impl Module { let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); let d = mem::take(args[3]).cast::(); - let a = args[0].downcast_mut::().unwrap(); + let mut a = args[0].write_lock::().unwrap(); - func(a, b, c, d).map(Dynamic::from) + func(&mut a, b, c, d).map(Dynamic::from) }; let arg_types = [ TypeId::of::(), diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index c0bea7ee..5ca3b1c7 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -34,7 +34,7 @@ fn pad( _: &Module, args: &mut [&mut Dynamic], ) -> FuncReturn<()> { - let len = *args[1].downcast_ref::().unwrap(); + let len = *args[1].read_lock::().unwrap(); // Check if array will be over max size limit #[cfg(not(feature = "unchecked"))] @@ -52,7 +52,7 @@ fn pad( if len > 0 { let item = args[2].clone(); - let list = args[0].downcast_mut::().unwrap(); + let mut list = args[0].write_lock::().unwrap(); if len as usize > list.len() { list.resize(len as usize, item); diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 6ddc2b0c..b50d6915 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -228,7 +228,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str "pad", &[TypeId::of::(), TypeId::of::(), TypeId::of::()], |_engine: &Engine, _: &Module, args: &mut [&mut Dynamic]| { - let len = *args[1].downcast_ref::< INT>().unwrap(); + let len = *args[1].read_lock::< INT>().unwrap(); // Check if string will be over max size limit #[cfg(not(feature = "unchecked"))] @@ -243,7 +243,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str if len > 0 { let ch = mem::take(args[2]).cast::(); - let s = args[0].downcast_mut::().unwrap(); + let mut s = args[0].write_lock::().unwrap(); let orig_len = s.chars().count(); diff --git a/src/scope.rs b/src/scope.rs index 5693554c..b095f782 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -333,7 +333,7 @@ impl<'a> Scope<'a> { .iter() .rev() .find(|Entry { name: key, .. }| name == key) - .and_then(|Entry { value, .. }| value.downcast_ref::().cloned()) + .and_then(|Entry { value, .. }| value.read::()) } /// Update the value of the named entry. diff --git a/src/token.rs b/src/token.rs index 7d1a07ba..bd496afb 100644 --- a/src/token.rs +++ b/src/token.rs @@ -2,7 +2,7 @@ use crate::engine::{ Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, - KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF, + KEYWORD_SHARED, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF, }; use crate::error::LexError; @@ -507,7 +507,7 @@ impl Token { | "async" | "await" | "yield" => Reserved(syntax.into()), KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR - | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS => Reserved(syntax.into()), + | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_SHARED | KEYWORD_THIS => Reserved(syntax.into()), _ => return None, }) From aa87a7f5efee45f712e20e4601e5954c26ec2e2f Mon Sep 17 00:00:00 2001 From: Ilya Lakhin Date: Fri, 31 Jul 2020 05:34:20 +0700 Subject: [PATCH 29/64] Fixes in Engine to properly interpret Shared Dynamic --- src/any.rs | 43 +++++++++++-- src/engine.rs | 105 +++++++++++++++++++------------ src/fn_call.rs | 1 - src/fn_native.rs | 2 +- src/fn_register.rs | 2 +- src/parser.rs | 28 +++++++-- src/token.rs | 11 +++- tests/closures.rs | 151 ++++++++++++++++++++++++++++++++++++++++++++- 8 files changed, 288 insertions(+), 55 deletions(-) diff --git a/src/any.rs b/src/any.rs index a7983030..7990ff10 100644 --- a/src/any.rs +++ b/src/any.rs @@ -18,7 +18,7 @@ use crate::stdlib::{ boxed::Box, fmt, string::String, - ops::{Deref, DerefMut} + ops::{DerefMut, Deref} }; #[cfg(not(feature = "sync"))] @@ -259,11 +259,13 @@ impl Dynamic { /// If the `Dynamic` is a Shared variant checking is performed on /// top of it's internal value. pub fn is(&self) -> bool { - self.type_id() == TypeId::of::() - || match self.0 { - Union::Str(_) => TypeId::of::() == TypeId::of::(), - _ => false, - } + let mut target_type_id = TypeId::of::(); + + if target_type_id == TypeId::of::() { + target_type_id = TypeId::of::(); + } + + self.type_id() == target_type_id } /// Get the TypeId of the value held by this `Dynamic`. @@ -606,6 +608,7 @@ impl Dynamic { _ => None, }; } + #[cfg(not(feature = "no_float"))] if type_id == TypeId::of::() { return match self.0 { @@ -613,30 +616,35 @@ impl Dynamic { _ => None, }; } + if type_id == TypeId::of::() { return match self.0 { Union::Bool(value) => unsafe_try_cast(value), _ => None, }; } + if type_id == TypeId::of::() { return match self.0 { Union::Str(value) => unsafe_try_cast(value), _ => None, }; } + if type_id == TypeId::of::() { return match self.0 { Union::Str(value) => unsafe_try_cast(value.into_owned()), _ => None, }; } + if type_id == TypeId::of::() { return match self.0 { Union::Char(value) => unsafe_try_cast(value), _ => None, }; } + #[cfg(not(feature = "no_index"))] if type_id == TypeId::of::() { return match self.0 { @@ -644,6 +652,7 @@ impl Dynamic { _ => None, }; } + #[cfg(not(feature = "no_object"))] if type_id == TypeId::of::() { return match self.0 { @@ -651,12 +660,14 @@ impl Dynamic { _ => None, }; } + if type_id == TypeId::of::() { return match self.0 { Union::FnPtr(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), _ => None, }; } + if type_id == TypeId::of::<()>() { return match self.0 { Union::Unit(value) => unsafe_try_cast(value), @@ -951,6 +962,7 @@ impl Dynamic { pub fn as_int(&self) -> Result { match self.0 { Union::Int(n) => Ok(n), + Union::Shared(_) => self.read::().ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -961,6 +973,7 @@ impl Dynamic { pub fn as_float(&self) -> Result { match self.0 { Union::Float(n) => Ok(n), + Union::Shared(_) => self.read::().ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -970,6 +983,7 @@ impl Dynamic { pub fn as_bool(&self) -> Result { match self.0 { Union::Bool(b) => Ok(b), + Union::Shared(_) => self.read::().ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -979,12 +993,15 @@ impl Dynamic { pub fn as_char(&self) -> Result { match self.0 { Union::Char(n) => Ok(n), + Union::Shared(_) => self.read::().ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } /// Cast the `Dynamic` as a string and return the string slice. /// Returns the name of the actual type if the cast fails. + /// + /// Cast is failing if `self` is Shared Dynamic pub fn as_str(&self) -> Result<&str, &'static str> { match &self.0 { Union::Str(s) => Ok(s), @@ -1006,6 +1023,20 @@ impl Dynamic { match self.0 { Union::Str(s) => Ok(s), Union::FnPtr(f) => Ok(f.take_data().0), + Union::Shared(cell) => { + #[cfg(not(feature = "sync"))] + match &cell.container.borrow().deref().0 { + Union::Str(s) => Ok(s.clone()), + Union::FnPtr(f) => Ok(f.clone().take_data().0), + _ => Err(cell.value_type_name), + } + #[cfg(feature = "sync")] + match &cell.container.read().deref().0 { + Union::Str(s) => Ok(s.clone()), + Union::FnPtr(f) => Ok(f.clone().take_data().0), + _ => Err(cell.value_type_name), + } + } _ => Err(self.type_name()), } } diff --git a/src/engine.rs b/src/engine.rs index 06ff8d7f..8f727211 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,6 +1,6 @@ //! Main module defining the script evaluation `Engine`. -use crate::any::{map_std_type_name, Dynamic, Union}; +use crate::any::{map_std_type_name, Dynamic, Union, DynamicWriteLock}; use crate::calc_fn_hash; use crate::fn_call::run_builtin_op_assignment; use crate::fn_native::{CallableFunction, Callback, FnPtr}; @@ -39,6 +39,7 @@ use crate::stdlib::{ iter::{empty, once}, string::{String, ToString}, vec::Vec, + ops::DerefMut, }; #[cfg(not(feature = "no_index"))] @@ -123,6 +124,9 @@ pub enum ChainType { pub enum Target<'a> { /// The target is a mutable reference to a `Dynamic` value somewhere. Ref(&'a mut Dynamic), + /// The target is a mutable reference to a Shared `Dynamic` value. + /// It holds the access guard and the original container both for cloning purposes + LockGuard((DynamicWriteLock<'a, Dynamic>, Dynamic)), /// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects). Value(Dynamic), /// The target is a character inside a String. @@ -137,6 +141,7 @@ impl Target<'_> { pub fn is_ref(&self) -> bool { match self { Self::Ref(_) => true, + Self::LockGuard(_) => true, Self::Value(_) => false, #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, _) => false, @@ -146,6 +151,7 @@ impl Target<'_> { pub fn is_value(&self) -> bool { match self { Self::Ref(_) => false, + Self::LockGuard(_) => false, Self::Value(_) => true, #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, _) => false, @@ -156,6 +162,7 @@ impl Target<'_> { pub fn is(&self) -> bool { match self { Target::Ref(r) => r.is::(), + Target::LockGuard((r, _)) => r.is::(), Target::Value(r) => r.is::(), #[cfg(not(feature = "no_index"))] Target::StringChar(_, _, _) => TypeId::of::() == TypeId::of::(), @@ -165,6 +172,7 @@ impl Target<'_> { pub fn clone_into_dynamic(self) -> Dynamic { match self { Self::Ref(r) => r.clone(), // Referenced value is cloned + Self::LockGuard((_, orig)) => orig, // Return original container of the Shared Dynamic Self::Value(v) => v, // Owned value is simply taken #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, ch) => ch, // Character is taken @@ -174,6 +182,7 @@ impl Target<'_> { pub fn as_mut(&mut self) -> &mut Dynamic { match self { Self::Ref(r) => *r, + Self::LockGuard((r, _)) => r.deref_mut(), Self::Value(ref mut r) => r, #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, ref mut r) => r, @@ -184,13 +193,16 @@ impl Target<'_> { pub fn set_value(&mut self, new_val: Dynamic) -> Result<(), Box> { match self { Self::Ref(r) => **r = new_val, + Self::LockGuard((r, _)) => **r = new_val, Self::Value(_) => { return Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS( Position::none(), ))) } #[cfg(not(feature = "no_index"))] - Self::StringChar(Dynamic(Union::Str(ref mut s)), index, _) => { + Self::StringChar(string, index, _) if string.is::() => { + let mut s = string.write_lock::().unwrap(); + // Replace the character at the specified index position let new_ch = new_val .as_char() @@ -216,7 +228,13 @@ impl Target<'_> { #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] impl<'a> From<&'a mut Dynamic> for Target<'a> { fn from(value: &'a mut Dynamic) -> Self { - Self::Ref(value) + if value.is_shared() { + // clone is cheap since it holds Arc/Rw under the hood + let container = value.clone(); + Self::LockGuard((value.write_lock::().unwrap(), container)) + } else { + Self::Ref(value) + } } } #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] @@ -676,15 +694,46 @@ impl Engine { } // xxx[rhs] = new_val _ if _new_val.is_some() => { - let mut new_val = _new_val.unwrap(); let mut idx_val2 = idx_val.clone(); + // `next` is introduced to bypass double mutable borrowing of target + #[cfg(not(feature = "no_index"))] + let mut next: Option<(u8, Dynamic)>; + match self.get_indexed_mut(state, lib, target, idx_val, pos, true, level) { // Indexed value is an owned value - the only possibility is an indexer // Try to call an index setter #[cfg(not(feature = "no_index"))] Ok(obj_ptr) if obj_ptr.is_value() => { - let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val]; + next = Some((1, _new_val.unwrap())); + } + // Indexed value is a reference - update directly + Ok(ref mut obj_ptr) => { + obj_ptr + .set_value(_new_val.unwrap()) + .map_err(|err| err.new_position(rhs.position()))?; + + #[cfg(not(feature = "no_index"))] + { + next = None; + } + } + Err(err) => match *err { + // No index getter - try to call an index setter + #[cfg(not(feature = "no_index"))] + EvalAltResult::ErrorIndexingType(_, _) => { + next = Some((2, _new_val.unwrap())); + } + // Error + err => return Err(Box::new(err)), + }, + }; + + #[cfg(not(feature = "no_index"))] + match &mut next { + // next step is custom index setter call + Some((1, _new_val)) => { + let args = &mut [target.as_mut(), &mut idx_val2, _new_val]; self.exec_fn_call( state, lib, FN_IDX_SET, true, 0, args, is_ref, true, false, @@ -698,27 +747,20 @@ impl Engine { _ => Err(err), })?; } - // Indexed value is a reference - update directly - Ok(ref mut obj_ptr) => { - obj_ptr - .set_value(new_val) - .map_err(|err| err.new_position(rhs.position()))?; - } - Err(err) => match *err { - // No index getter - try to call an index setter - #[cfg(not(feature = "no_index"))] - EvalAltResult::ErrorIndexingType(_, _) => { - let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val]; - self.exec_fn_call( - state, lib, FN_IDX_SET, true, 0, args, is_ref, true, false, - None, level, - )?; - } - // Error - err => return Err(Box::new(err)), - }, + // next step is custom index setter call in case of error + Some((2, _new_val)) => { + let args = &mut [target.as_mut(), &mut idx_val2, _new_val]; + + self.exec_fn_call( + state, lib, FN_IDX_SET, true, 0, args, is_ref, true, false, + None, level, + )?; + } + None => (), + _ => unreachable!() } + Ok(Default::default()) } // xxx[rhs] @@ -835,11 +877,10 @@ impl Engine { .map_err(|err| err.new_position(*pos))?; let val = &mut val; - let target = &mut val.into(); let (result, may_be_changed) = self .eval_dot_index_chain_helper( - state, lib, this_ptr, target, expr, idx_values, next_chain, + state, lib, this_ptr, &mut val.into(), expr, idx_values, next_chain, level, _new_val, ) .map_err(|err| err.new_position(*pos))?; @@ -1069,18 +1110,6 @@ impl Engine { let val = target.as_mut(); - // if val.is_shared() { - // return self.get_indexed_mut( - // state, - // lib, - // &mut Target::Value(val.read::().unwrap()), - // idx, - // idx_pos, - // create, - // level, - // ); - // } - match val { #[cfg(not(feature = "no_index"))] Dynamic(Union::Array(arr)) => { diff --git a/src/fn_call.rs b/src/fn_call.rs index 6ca46046..19f3fd33 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -699,7 +699,6 @@ impl Engine { } // Handle shared() - #[cfg(not(feature = "no_shared"))] if name == KEYWORD_SHARED && args_expr.len() == 1 { let expr = args_expr.get(0).unwrap(); let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; diff --git a/src/fn_native.rs b/src/fn_native.rs index e51bf71b..54f3da08 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -18,7 +18,7 @@ use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, iter::empty, string::Stri use crate::stdlib::mem; #[cfg(not(feature = "sync"))] -use crate::stdlib::{rc::Rc, cell::{RefCell, Ref, RefMut}}; +use crate::stdlib::{rc::Rc, cell::RefCell}; #[cfg(feature = "sync")] use crate::stdlib::sync::{Arc, RwLock}; diff --git a/src/fn_register.rs b/src/fn_register.rs index ddcddff3..eef4e66b 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -114,7 +114,7 @@ pub fn by_value(data: &mut Dynamic) -> T { ref_T.clone() } else if TypeId::of::() == TypeId::of::() { // If T is String, data must be ImmutableString, so map directly to it - *unsafe_cast_box(Box::new(data.as_str().unwrap().to_string())).unwrap() + *unsafe_cast_box(Box::new(data.clone().take_string().unwrap())).unwrap() } else { // We consume the argument and then replace it with () - the argument is not supposed to be used again. // This way, we avoid having to clone the argument again, because it is already a clone when passed here. diff --git a/src/parser.rs b/src/parser.rs index 3068bad0..2128f38c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -411,6 +411,11 @@ struct ParseState<'e> { /// Tracks a list of external variables (variables that are not explicitly declared in the scope). #[cfg(not(feature = "no_capture"))] externals: HashMap, + /// An indicator that prevents variables capturing into externals one time. + /// If set to true the next call of `access_var` will not capture the variable. + /// All consequent calls to `access_var` will not be affected + #[cfg(not(feature = "no_capture"))] + capture: bool, /// Encapsulates a local stack with variable names to simulate an actual runtime scope. modules: Vec, /// Maximum levels of expression nesting. @@ -436,6 +441,8 @@ impl<'e> ParseState<'e> { max_function_expr_depth, #[cfg(not(feature = "no_capture"))] externals: Default::default(), + #[cfg(not(feature = "no_capture"))] + capture: true, stack: Default::default(), modules: Default::default(), } @@ -458,8 +465,12 @@ impl<'e> ParseState<'e> { .and_then(|(i, _)| NonZeroUsize::new(i + 1)); #[cfg(not(feature = "no_capture"))] - if index.is_none() && !self.externals.contains_key(name) { - self.externals.insert(name.to_string(), pos); + if self.capture { + if index.is_none() && !self.externals.contains_key(name) { + self.externals.insert(name.to_string(), pos); + } + } else { + self.capture = true } index @@ -2171,9 +2182,18 @@ fn parse_binary_op( let (op_token, pos) = input.next().unwrap(); - let rhs = parse_unary(input, state, lib, settings)?; + let next = input.peek().unwrap(); + let next_precedence = next.0.precedence(custom); - let next_precedence = input.peek().unwrap().0.precedence(custom); + #[cfg(any(not(feature = "no_object"), not(feature = "no_capture")))] + if op_token == Token::Period { + if let (Token::Identifier(_), _) = next { + // prevents capturing of the object properties as vars: xxx. + state.capture = false; + } + } + + let rhs = parse_unary(input, state, lib, settings)?; // Bind to right if the next operator has higher precedence // If same precedence, then check if the operator binds right diff --git a/src/token.rs b/src/token.rs index bd496afb..71ec09a6 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1430,13 +1430,20 @@ fn get_identifier( /// Is this keyword allowed as a function? #[inline(always)] pub fn is_keyword_function(name: &str) -> bool { - name == KEYWORD_PRINT + let mut result = name == KEYWORD_PRINT || name == KEYWORD_DEBUG || name == KEYWORD_TYPE_OF || name == KEYWORD_EVAL || name == KEYWORD_FN_PTR || name == KEYWORD_FN_PTR_CALL - || name == KEYWORD_FN_PTR_CURRY + || name == KEYWORD_FN_PTR_CURRY; + + #[cfg(not(feature = "no-shared"))] + { + result = result || name == KEYWORD_SHARED; + } + + result } pub fn is_valid_identifier(name: impl Iterator) -> bool { diff --git a/tests/closures.rs b/tests/closures.rs index 3aec4865..ae0c6535 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -1,6 +1,6 @@ #![cfg(not(feature = "no_function"))] -use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, Module, INT}; -use std::any::TypeId; +use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, Module, INT, Array}; +use std::any::{TypeId, Any}; #[test] fn test_fn_ptr_curry_call() -> Result<(), Box> { @@ -58,3 +58,150 @@ fn test_closures() -> Result<(), Box> { Ok(()) } + +#[test] +#[cfg(not(feature = "no_shared"))] +fn test_shared() -> Result<(), Box> { + let engine = Engine::new(); + + // assert_eq!( + // engine.eval::( + // r#" + // shared(42) + // "# + // )?, + // 42 + // ); + // + // assert_eq!( + // engine.eval::( + // r#" + // shared(true) + // "# + // )?, + // true + // ); + // + // #[cfg(not(feature = "no_float"))] + // assert_eq!( + // engine.eval::( + // r#" + // shared(4.2) + // "# + // )?, + // 4.2 + // ); + // + // assert_eq!( + // engine.eval::( + // r#" + // shared("test") + // "# + // )?, + // "test" + // ); + // + // #[cfg(not(feature = "no_index"))] + // { + // assert_eq!( + // engine.eval::( + // r#" + // let x = shared([1, 2, 3]); + // let y = shared([4, 5]); + // x + y + // "# + // )?.len(), + // 5 + // ); + // + // assert_eq!( + // engine.eval::( + // r" + // let x = shared([2, 9]); + // x.insert(-1, 1); + // x.insert(999, 3); + // + // let r = x.remove(2); + // + // let y = shared([4, 5]); + // x.append(y); + // + // x.len + r + // " + // )?, + // 14 + // ); + // + // assert_eq!( + // engine.eval::( + // r#" + // let x = shared([1, 2, 3]); + // + // if x[0] + x[2] == 4 { + // true + // } else { + // false + // } + // "# + // )?, + // true + // ); + // } + // + // #[cfg(not(feature = "no_object"))] + // assert_eq!( + // engine.eval::(r#" + // let y = shared(#{a: 1, b: 2, c: 3}); + // y.c = shared(5); + // y.c + // "#)?, + // 5 + // ); + // + // #[cfg(not(feature = "no_object"))] + // assert_eq!( + // engine.eval::(r#" + // let y = shared(#{a: 1, b: 2, c: shared(3)}); + // let c = y.c; + // c = 5;// "c" still holds Dynamic Shared + // y.c + // "#)?, + // 5 + // ); + // + // #[cfg(not(feature = "no_capture"))] + // assert_eq!( + // engine.eval::(r#" + // let x = shared(1); + // (|| x = x + 41).call(); + // x + // "#)?, + // 42 + // ); + + #[cfg(all(not(feature = "no_object"), not(feature = "no_capture")))] + assert_eq!( + engine.eval::(r#" + // let x = shared(#{a: 1, b: shared(2), c: 3}); + // let a = x.a; + // let b = x.b; + // a = 100; + // b = 20; + // + // let f = |a| { + // x.c = x.a + x.b;// + a; + // }; + // + // f.call(20); + // + // x.c + + let x = #{a: 1, b: 2}; + + x.a + x.b + "#)?, + 42 + ); + + Ok(()) +} From 060dd33046136d2f36b0380442116a433c47bf0d Mon Sep 17 00:00:00 2001 From: Ilya Lakhin Date: Fri, 31 Jul 2020 10:44:57 +0700 Subject: [PATCH 30/64] Shared Dynamic tests and fixes in Engine; Also fixed a bug in Parser variable capturing --- src/any.rs | 2 +- src/parser.rs | 7 +- tests/closures.rs | 359 +++++++++++++++++++++++++++++----------------- 3 files changed, 231 insertions(+), 137 deletions(-) diff --git a/src/any.rs b/src/any.rs index 7990ff10..db2ada1b 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1031,7 +1031,7 @@ impl Dynamic { _ => Err(cell.value_type_name), } #[cfg(feature = "sync")] - match &cell.container.read().deref().0 { + match &cell.container.read().unwrap().deref().0 { Union::Str(s) => Ok(s.clone()), Union::FnPtr(f) => Ok(f.clone().take_data().0), _ => Err(cell.value_type_name), diff --git a/src/parser.rs b/src/parser.rs index 2128f38c..1d9f26dd 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2182,12 +2182,9 @@ fn parse_binary_op( let (op_token, pos) = input.next().unwrap(); - let next = input.peek().unwrap(); - let next_precedence = next.0.precedence(custom); - #[cfg(any(not(feature = "no_object"), not(feature = "no_capture")))] if op_token == Token::Period { - if let (Token::Identifier(_), _) = next { + if let (Token::Identifier(_), _) = input.peek().unwrap() { // prevents capturing of the object properties as vars: xxx. state.capture = false; } @@ -2195,6 +2192,8 @@ fn parse_binary_op( let rhs = parse_unary(input, state, lib, settings)?; + let next_precedence = input.peek().unwrap().0.precedence(custom); + // Bind to right if the next operator has higher precedence // If same precedence, then check if the operator binds right let rhs = if (precedence == next_precedence && bind_right) || precedence < next_precedence { diff --git a/tests/closures.rs b/tests/closures.rs index ae0c6535..25166ee1 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -1,5 +1,5 @@ #![cfg(not(feature = "no_function"))] -use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, Module, INT, Array}; +use rhai::{Dynamic, Engine, EvalAltResult, RegisterFn, FnPtr, Module, INT, Array}; use std::any::{TypeId, Any}; #[test] @@ -62,146 +62,241 @@ fn test_closures() -> Result<(), Box> { #[test] #[cfg(not(feature = "no_shared"))] fn test_shared() -> Result<(), Box> { - let engine = Engine::new(); + let mut engine = Engine::new(); - // assert_eq!( - // engine.eval::( - // r#" - // shared(42) - // "# - // )?, - // 42 - // ); - // - // assert_eq!( - // engine.eval::( - // r#" - // shared(true) - // "# - // )?, - // true - // ); - // - // #[cfg(not(feature = "no_float"))] - // assert_eq!( - // engine.eval::( - // r#" - // shared(4.2) - // "# - // )?, - // 4.2 - // ); - // - // assert_eq!( - // engine.eval::( - // r#" - // shared("test") - // "# - // )?, - // "test" - // ); - // - // #[cfg(not(feature = "no_index"))] - // { - // assert_eq!( - // engine.eval::( - // r#" - // let x = shared([1, 2, 3]); - // let y = shared([4, 5]); - // x + y - // "# - // )?.len(), - // 5 - // ); - // - // assert_eq!( - // engine.eval::( - // r" - // let x = shared([2, 9]); - // x.insert(-1, 1); - // x.insert(999, 3); - // - // let r = x.remove(2); - // - // let y = shared([4, 5]); - // x.append(y); - // - // x.len + r - // " - // )?, - // 14 - // ); - // - // assert_eq!( - // engine.eval::( - // r#" - // let x = shared([1, 2, 3]); - // - // if x[0] + x[2] == 4 { - // true - // } else { - // false - // } - // "# - // )?, - // true - // ); - // } - // - // #[cfg(not(feature = "no_object"))] - // assert_eq!( - // engine.eval::(r#" - // let y = shared(#{a: 1, b: 2, c: 3}); - // y.c = shared(5); - // y.c - // "#)?, - // 5 - // ); - // - // #[cfg(not(feature = "no_object"))] - // assert_eq!( - // engine.eval::(r#" - // let y = shared(#{a: 1, b: 2, c: shared(3)}); - // let c = y.c; - // c = 5;// "c" still holds Dynamic Shared - // y.c - // "#)?, - // 5 - // ); - // - // #[cfg(not(feature = "no_capture"))] - // assert_eq!( - // engine.eval::(r#" - // let x = shared(1); - // (|| x = x + 41).call(); - // x - // "#)?, - // 42 - // ); + assert_eq!( + engine.eval::( + r#" + shared(42) + "# + )?, + 42 + ); - #[cfg(all(not(feature = "no_object"), not(feature = "no_capture")))] + assert_eq!( + engine.eval::( + r#" + shared(true) + "# + )?, + true + ); + + #[cfg(not(feature = "no_float"))] + assert_eq!( + engine.eval::( + r#" + shared(4.2) + "# + )?, + 4.2 + ); + + assert_eq!( + engine.eval::( + r#" + shared("test") + "# + )?, + "test" + ); + + assert_eq!( + engine.eval::( + r#" + shared('x') + "# + )?, + 'x' + ); + + assert_eq!( + engine.eval::( + r#" + let s = shared("test"); + let i = shared(0); + i = 2; + + s[i] = 'S'; + s + "# + )?, + "teSt" + ); + + #[cfg(not(feature = "no_index"))] + { + assert_eq!( + engine.eval::( + r#" + let x = shared([1, 2, 3]); + let y = shared([4, 5]); + x + y + "# + )?.len(), + 5 + ); + + assert_eq!( + engine.eval::( + r" + let x = shared([2, 9]); + x.insert(-1, 1); + x.insert(999, 3); + + let r = x.remove(2); + + let y = shared([4, 5]); + x.append(y); + + x.len + r + " + )?, + 14 + ); + + assert_eq!( + engine.eval::( + r#" + let x = shared([1, 2, 3]); + + if x[0] + x[2] == 4 { + true + } else { + false + } + "# + )?, + true + ); + + assert_eq!( + engine.eval::( + r#" + let x = shared([1, 2, 3]); + let y = shared(()); + + (|| { + for i in x { + y = i * 10; + } + }).call(); + + y + "# + )?, + 30 + ); + } + + #[cfg(not(feature = "no_object"))] assert_eq!( engine.eval::(r#" - // let x = shared(#{a: 1, b: shared(2), c: 3}); - // let a = x.a; - // let b = x.b; - // a = 100; - // b = 20; - // - // let f = |a| { - // x.c = x.a + x.b;// + a; - // }; - // - // f.call(20); - // - // x.c + let y = shared(#{a: 1, b: 2, c: 3}); + y.c = shared(5); + y.c + "#)?, + 5 + ); - let x = #{a: 1, b: 2}; + #[cfg(not(feature = "no_object"))] + assert_eq!( + engine.eval::(r#" + let y = shared(#{a: 1, b: 2, c: shared(3)}); + let c = y.c; + c = 5;// "c" holds Dynamic Shared + y.c + "#)?, + 5 + ); - x.a + x.b + #[cfg(not(feature = "no_capture"))] + assert_eq!( + engine.eval::(r#" + let x = shared(1); + (|| x = x + 41).call(); + x "#)?, 42 ); + #[cfg(all(not(feature = "no_object"), not(feature = "no_capture")))] + assert_eq!( + engine.eval::(r#" + let x = shared(#{a: 1, b: shared(2), c: 3}); + let a = x.a; + let b = x.b; + a = 100; // does not hold reference to x.a + b = 20; // does hold reference to x.b + + let f = |a| { + x.c = x.a + x.b + a; + }; + + f.call(21); + + x.c + "#)?, + 42 + ); + + // Register a binary function named `foo` + engine.register_fn("custom_addition", |x: INT, y: INT| x + y); + + assert_eq!( + engine.eval::(r#" + custom_addition(shared(20), shared(22)) + "#)?, + 42 + ); + + #[derive(Clone)] + struct TestStruct { + x: INT, + } + + impl TestStruct { + fn update(&mut self) { + self.x += 1000; + } + + fn merge(&mut self, other: Self) { + self.x += other.x; + } + + fn get_x(&mut self) -> INT { + self.x + } + + fn set_x(&mut self, new_x: INT) { + self.x = new_x; + } + + fn new() -> Self { + TestStruct { x: 1 } + } + } + + engine.register_type::(); + + engine.register_get_set("x", TestStruct::get_x, TestStruct::set_x); + engine.register_fn("update", TestStruct::update); + engine.register_fn("merge", TestStruct::merge); + engine.register_fn("new_ts", TestStruct::new); + + assert_eq!( + engine.eval::( + r" + let a = shared(new_ts()); + + a.x = 100; + a.update(); + // a.merge(a); + a.x + " + )?, + 1100 + ); + Ok(()) } From cb005506e2f3984dc3e8f5c0f225f3d0ec25ab0b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 31 Jul 2020 12:11:16 +0800 Subject: [PATCH 31/64] Simplify function calling. --- src/engine.rs | 15 ++-- src/fn_call.rs | 192 +++++++++++++++++++++++++++-------------------- src/fn_native.rs | 10 +++ src/optimize.rs | 12 +-- 4 files changed, 130 insertions(+), 99 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 6aa1f9c2..dc916876 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1179,7 +1179,6 @@ impl Engine { #[cfg(not(feature = "no_index"))] Dynamic(Union::Array(mut rhs_value)) => { let op = "=="; - let mut scope = Scope::new(); // Call the `==` operator to compare each value for value in rhs_value.iter_mut() { @@ -1190,13 +1189,13 @@ impl Engine { let hash = calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id())); - let (r, _) = self - .call_fn_raw( - &mut scope, mods, state, lib, op, hash, args, false, false, false, - def_value, level, - ) - .map_err(|err| err.new_position(rhs.position()))?; - if r.as_bool().unwrap_or(false) { + if self + .call_native_fn(state, lib, op, hash, args, false, false, def_value) + .map_err(|err| err.new_position(rhs.position()))? + .0 + .as_bool() + .unwrap_or(false) + { return Ok(true.into()); } } diff --git a/src/fn_call.rs b/src/fn_call.rs index 6672d746..91c8e67b 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -41,7 +41,7 @@ use crate::stdlib::{ format, iter::{empty, once}, mem, - string::ToString, + string::{String, ToString}, vec::Vec, }; @@ -67,43 +67,69 @@ fn extract_prop_from_setter(_fn_name: &str) -> Option<&str> { None } -/// This function replaces the first argument of a method call with a clone copy. -/// This is to prevent a pure function unintentionally consuming the first argument. -fn normalize_first_arg<'a>( - normalize: bool, - this_copy: &mut Dynamic, - old_this_ptr: &mut Option<&'a mut Dynamic>, - args: &mut FnCallArgs<'a>, -) { - // Only do it for method calls with arguments. - if !normalize || args.is_empty() { - return; - } - - // Clone the original value. - *this_copy = args[0].clone(); - - // Replace the first reference with a reference to the clone, force-casting the lifetime. - // Keep the original reference. Must remember to restore it later with `restore_first_arg_of_method_call`. - // - // # Safety - // - // Blindly casting a a reference to another lifetime saves on allocations and string cloning, - // but must be used with the utmost care. - // - // We can do this here because, at the end of this scope, we'd restore the original reference - // with `restore_first_arg_of_method_call`. Therefore this shorter lifetime does not get "out". - let this_pointer = mem::replace(args.get_mut(0).unwrap(), unsafe { - mem::transmute(this_copy) - }); - - *old_this_ptr = Some(this_pointer); +/// A type that temporarily stores a mutable reference to a `Dynamic`, +/// replacing it with a cloned copy. +#[derive(Debug, Default)] +struct ArgBackup<'a> { + orig_mut: Option<&'a mut Dynamic>, + value_copy: Dynamic, } -/// This function restores the first argument that was replaced by `normalize_first_arg_of_method_call`. -fn restore_first_arg<'a>(old_this_ptr: Option<&'a mut Dynamic>, args: &mut FnCallArgs<'a>) { - if let Some(this_pointer) = old_this_ptr { - args[0] = this_pointer; +impl<'a> ArgBackup<'a> { + /// This function replaces the first argument of a method call with a clone copy. + /// This is to prevent a pure function unintentionally consuming the first argument. + /// + /// `restore_first_arg` must be called before the end of the scope to prevent the shorter lifetime from leaking. + /// + /// # Safety + /// + /// This method blindly casts a reference to another lifetime, which saves allocation and string cloning. + /// + /// If `restore_first_arg` is called before the end of the scope, the shorter lifetime will not leak. + fn change_first_arg_to_copy(&mut self, normalize: bool, args: &mut FnCallArgs<'a>) { + // Only do it for method calls with arguments. + if !normalize || args.is_empty() { + return; + } + + // Clone the original value. + self.value_copy = args[0].clone(); + + // Replace the first reference with a reference to the clone, force-casting the lifetime. + // Must remember to restore it later with `restore_first_arg`. + // + // # Safety + // + // Blindly casting a reference to another lifetime saves allocation and string cloning, + // but must be used with the utmost care. + // + // We can do this here because, before the end of this scope, we'd restore the original reference + // via `restore_first_arg`. Therefore this shorter lifetime does not leak. + self.orig_mut = Some(mem::replace(args.get_mut(0).unwrap(), unsafe { + mem::transmute(&mut self.value_copy) + })); + } + + /// This function restores the first argument that was replaced by `change_first_arg_to_copy`. + /// + /// # Safety + /// + /// If `change_first_arg_to_copy` has been called, this function **MUST** be called _BEFORE_ exiting + /// the current scope. Otherwise it is undefined behavior as the shorter lifetime will leak. + fn restore_first_arg(&mut self, args: &mut FnCallArgs<'a>) { + if let Some(this_pointer) = self.orig_mut.take() { + args[0] = this_pointer; + } + } +} + +impl Drop for ArgBackup<'_> { + fn drop(&mut self) { + // Panic if the shorter lifetime leaks. + assert!( + self.orig_mut.is_none(), + "MutBackup::restore has not been called prior to existing this scope" + ); } } @@ -138,34 +164,21 @@ impl Engine { /// Function call arguments be _consumed_ when the function requires them to be passed by value. /// All function arguments not in the first position are always passed by value and thus consumed. /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! - pub(crate) fn call_fn_raw( + pub(crate) fn call_native_fn( &self, - _scope: &mut Scope, - _mods: &mut Imports, state: &mut State, lib: &Module, fn_name: &str, hash_fn: u64, args: &mut FnCallArgs, is_ref: bool, - _is_method: bool, pub_only: bool, def_val: Option, - _level: usize, ) -> Result<(Dynamic, bool), Box> { self.inc_operations(state)?; - // Check for stack overflow - #[cfg(not(feature = "no_function"))] - #[cfg(not(feature = "unchecked"))] - if _level > self.limits.max_call_stack_depth { - return Err(Box::new( - EvalAltResult::ErrorStackOverflow(Position::none()), - )); - } - - // Search for the function - // First search registered native functions (can override packages) + // Search for the native function + // First search registered functions (can override packages) // Then search packages let func = self .global_module @@ -173,22 +186,19 @@ impl Engine { .or_else(|| self.packages.get_fn(hash_fn, pub_only)); if let Some(func) = func { - #[cfg(not(feature = "no_function"))] - let need_normalize = is_ref && (func.is_pure() || (func.is_script() && !_is_method)); - #[cfg(feature = "no_function")] - let need_normalize = is_ref && func.is_pure(); - - let mut this_copy: Dynamic = Default::default(); - let mut old_this_ptr: Option<&mut Dynamic> = None; + assert!(func.is_native()); // Calling pure function but the first argument is a reference? - normalize_first_arg(need_normalize, &mut this_copy, &mut old_this_ptr, args); + let mut backup: ArgBackup = Default::default(); + backup.change_first_arg_to_copy(is_ref && func.is_pure(), args); // Run external function - let result = func.get_native_fn()(self, lib, args)?; + let result = func.get_native_fn()(self, lib, args); // Restore the original reference - restore_first_arg(old_this_ptr, args); + backup.restore_first_arg(args); + + let result = result?; // See if the function match print/debug (which requires special processing) return Ok(match fn_name { @@ -320,6 +330,17 @@ impl Engine { args: &mut FnCallArgs, level: usize, ) -> Result> { + self.inc_operations(state)?; + + // Check for stack overflow + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "unchecked"))] + if level > self.limits.max_call_stack_depth { + return Err(Box::new( + EvalAltResult::ErrorStackOverflow(Position::none()), + )); + } + let orig_scope_level = state.scope_level; state.scope_level += 1; @@ -382,7 +403,7 @@ impl Engine { || self.packages.contains_fn(hash_fn, pub_only) } - /// Perform an actual function call, taking care of special functions + /// Perform an actual function call, native Rust or scripted, taking care of special functions. /// Position in `EvalAltResult` is `None` and must be set afterwards. /// /// ## WARNING @@ -470,33 +491,26 @@ impl Engine { )? } else { // Normal call of script function - map first argument to `this` - let mut first_copy: Dynamic = Default::default(); - let mut old_first: Option<&mut Dynamic> = None; - // The first argument is a reference? - normalize_first_arg(is_ref, &mut first_copy, &mut old_first, args); + let mut backup: ArgBackup = Default::default(); + backup.change_first_arg_to_copy(is_ref, args); let result = self.call_script_fn( scope, mods, state, lib, &mut None, fn_name, func, args, level, - )?; + ); // Restore the original reference - restore_first_arg(old_first, args); + backup.restore_first_arg(args); - result + result? }; Ok((result, false)) } // Normal native function call - _ => { - let mut scope = Scope::new(); - let mut mods = Imports::new(); - self.call_fn_raw( - &mut scope, &mut mods, state, lib, fn_name, hash_fn, args, is_ref, is_method, - pub_only, def_val, level, - ) - } + _ => self.call_native_fn( + state, lib, fn_name, hash_fn, args, is_ref, pub_only, def_val, + ), } } @@ -508,9 +522,21 @@ impl Engine { mods: &mut Imports, state: &mut State, lib: &Module, - script: &Dynamic, + script_expr: &Dynamic, + level: usize, ) -> Result> { - let script = script.as_str().map_err(|typ| { + self.inc_operations(state)?; + + // Check for stack overflow + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "unchecked"))] + if level > self.limits.max_call_stack_depth { + return Err(Box::new( + EvalAltResult::ErrorStackOverflow(Position::none()), + )); + } + + let script = script_expr.as_str().map_err(|typ| { EvalAltResult::ErrorMismatchOutputType( self.map_type_name(type_name::()).into(), typ.into(), @@ -782,12 +808,12 @@ impl Engine { let expr = args_expr.get(0).unwrap(); let script = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; let result = self - .eval_script_expr(scope, mods, state, lib, &script) + .eval_script_expr(scope, mods, state, lib, &script, level + 1) .map_err(|err| err.new_position(expr.position())); + // IMPORTANT! If the eval defines new variables in the current scope, + // all variable offsets from this point on will be mis-aligned. if scope.len() != prev_len { - // IMPORTANT! If the eval defines new variables in the current scope, - // all variable offsets from this point on will be mis-aligned. state.always_search = true; } diff --git a/src/fn_native.rs b/src/fn_native.rs index f6bb962e..064b82fa 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -280,6 +280,16 @@ impl CallableFunction { Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => false, } } + /// Is this a native Rust function? + pub fn is_native(&self) -> bool { + match self { + Self::Pure(_) | Self::Method(_) => true, + Self::Iterator(_) => true, + + #[cfg(not(feature = "no_function"))] + Self::Script(_) => false, + } + } /// Get the access mode. pub fn access(&self) -> FnAccess { match self { diff --git a/src/optimize.rs b/src/optimize.rs index 7c71aced..3244540b 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -3,7 +3,7 @@ use crate::any::Dynamic; use crate::calc_fn_hash; use crate::engine::{ - Engine, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF, + Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF, }; use crate::fn_native::FnPtr; use crate::module::Module; @@ -132,22 +132,18 @@ fn call_fn_with_constant_arguments( state .engine - .call_fn_raw( - &mut Scope::new(), - &mut Imports::new(), + .call_native_fn( &mut Default::default(), state.lib, fn_name, hash_fn, arg_values.iter_mut().collect::>().as_mut(), false, - false, true, None, - 0, ) - .map(|(v, _)| Some(v)) - .unwrap_or_else(|_| None) + .ok() + .map(|(v, _)| v) } /// Optimize a statement. From 49392d57d7d3dfe1cbb105c0c8ac0f2b8eee71c0 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 31 Jul 2020 12:40:16 +0800 Subject: [PATCH 32/64] Fix no_std feature. --- src/fn_call.rs | 4 ++-- src/scope.rs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/fn_call.rs b/src/fn_call.rs index 91c8e67b..da029c78 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -523,14 +523,14 @@ impl Engine { state: &mut State, lib: &Module, script_expr: &Dynamic, - level: usize, + _level: usize, ) -> Result> { self.inc_operations(state)?; // Check for stack overflow #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "unchecked"))] - if level > self.limits.max_call_stack_depth { + if _level > self.limits.max_call_stack_depth { return Err(Box::new( EvalAltResult::ErrorStackOverflow(Position::none()), )); diff --git a/src/scope.rs b/src/scope.rs index b2024564..a167a216 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -407,7 +407,6 @@ impl<'a> Scope<'a> { } /// Get an iterator to entries in the Scope. - #[cfg(not(feature = "no_module"))] pub(crate) fn into_iter(self) -> impl Iterator> { self.0.into_iter() } From ca64668e583ec7b34377a1db5a11294c5c2570ff Mon Sep 17 00:00:00 2001 From: Ilya Lakhin Date: Fri, 31 Jul 2020 11:41:22 +0700 Subject: [PATCH 33/64] take() keyword; shared test with registered functions with callbacks --- Cargo.toml | 2 +- src/any.rs | 6 ++++-- src/engine.rs | 1 + src/fn_call.rs | 5 ++++- src/token.rs | 9 +++++---- tests/closures.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++--- 6 files changed, 60 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 85b57974..0cc0e20d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ no_index = [] # no arrays and indexing no_object = [] # no custom objects no_function = [] # no script-defined functions no_capture = [] # no automatic read/write binding of anonymous function's local variables to it's external context -no_shared = [] # no explicit shared variables in the script code +no_shared = [] # no explicit shared() and take() functions in the script code no_module = [] # no modules internals = [] # expose internal data structures unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. diff --git a/src/any.rs b/src/any.rs index db2ada1b..1a82d237 100644 --- a/src/any.rs +++ b/src/any.rs @@ -568,7 +568,7 @@ impl Dynamic { /// /// ## Safety /// - /// Both situations normally shouldn't happen since all operations in Rhai + /// Both situations normally shouldn't happen since most operations in Rhai /// use pass-by-value data and the script executed in a single thread. /// /// # Example @@ -741,7 +741,9 @@ impl Dynamic { } /// Get a copy of a specific type to the `Dynamic`. - /// Casting to `Dynamic` just returns a reference to it. + /// Casting to `Dynamic` returns a clone of the value in case of NON-shared + /// Dynamic. In case of Shared Dynamic returns a clone of the inner data of + /// Shared Dynamic. /// Returns `None` if the cast fails. #[inline(always)] pub fn read(&self) -> Option { diff --git a/src/engine.rs b/src/engine.rs index 8f727211..4cc3b266 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -93,6 +93,7 @@ pub const KEYWORD_FN_PTR: &str = "Fn"; pub const KEYWORD_FN_PTR_CALL: &str = "call"; pub const KEYWORD_FN_PTR_CURRY: &str = "curry"; pub const KEYWORD_SHARED: &str = "shared"; +pub const KEYWORD_TAKE: &str = "take"; pub const KEYWORD_THIS: &str = "this"; pub const FN_TO_STRING: &str = "to_string"; #[cfg(not(feature = "no_object"))] diff --git a/src/fn_call.rs b/src/fn_call.rs index 19f3fd33..6f82df42 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -5,7 +5,7 @@ use crate::calc_fn_hash; use crate::engine::{ search_imports, search_namespace, search_scope_only, Engine, Imports, State, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_PRINT, - KEYWORD_TYPE_OF, KEYWORD_SHARED + KEYWORD_TYPE_OF, KEYWORD_SHARED, KEYWORD_TAKE }; use crate::error::ParseErrorType; use crate::fn_native::{FnCallArgs, FnPtr}; @@ -593,6 +593,9 @@ impl Engine { .into(), false, )) + } else if _fn_name == KEYWORD_TAKE { + // take call + return Ok((obj.read::().unwrap(), false)); } else { #[cfg(not(feature = "no_object"))] let redirected; diff --git a/src/token.rs b/src/token.rs index 71ec09a6..b0c77e38 100644 --- a/src/token.rs +++ b/src/token.rs @@ -2,7 +2,7 @@ use crate::engine::{ Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, - KEYWORD_SHARED, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF, + KEYWORD_SHARED, KEYWORD_TAKE, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF, }; use crate::error::LexError; @@ -503,11 +503,12 @@ impl Token { "===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "public" | "new" | "use" | "module" | "package" | "var" | "static" | "with" | "do" | "each" | "then" | "goto" | "exit" | "switch" | "match" | "case" | "try" | "catch" - | "default" | "void" | "null" | "nil" | "spawn" | "go" | "shared" | "sync" + | "default" | "void" | "null" | "nil" | "spawn" | "go" | "sync" | "async" | "await" | "yield" => Reserved(syntax.into()), KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR - | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_SHARED | KEYWORD_THIS => Reserved(syntax.into()), + | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_SHARED + | KEYWORD_TAKE |KEYWORD_THIS => Reserved(syntax.into()), _ => return None, }) @@ -1440,7 +1441,7 @@ pub fn is_keyword_function(name: &str) -> bool { #[cfg(not(feature = "no-shared"))] { - result = result || name == KEYWORD_SHARED; + result = result || name == KEYWORD_SHARED || name == KEYWORD_TAKE; } result diff --git a/tests/closures.rs b/tests/closures.rs index 25166ee1..6198bc26 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -116,8 +116,8 @@ fn test_shared() -> Result<(), Box> { let s = shared("test"); let i = shared(0); i = 2; - s[i] = 'S'; + s "# )?, @@ -283,6 +283,26 @@ fn test_shared() -> Result<(), Box> { engine.register_fn("update", TestStruct::update); engine.register_fn("merge", TestStruct::merge); engine.register_fn("new_ts", TestStruct::new); + engine. + register_raw_fn( + "mutate_with_cb", + &[ + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + ], + move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { + let fp = std::mem::take(args[2]).cast::(); + let mut value = args[1].clone(); + { + let mut lock = value.write_lock::().unwrap(); + *lock = *lock + 1; + } + let this_ptr = args.get_mut(0).unwrap(); + + fp.call_dynamic(engine, lib, Some(this_ptr), [value]) + }, + ); assert_eq!( engine.eval::( @@ -291,11 +311,33 @@ fn test_shared() -> Result<(), Box> { a.x = 100; a.update(); - // a.merge(a); + a.merge(a.take()); // take is important to prevent a deadlock + a.x " )?, - 1100 + 2200 + ); + + assert_eq!( + engine.eval::( + r" + let a = shared(new_ts()); + let b = shared(100); + + a.mutate_with_cb(b, |param| { + this.x = param; + param = 50; + this.update(); + }); + + a.update(); + a.x += b; + + a.x + " + )?, + 2151 ); Ok(()) From 4f771d904ab5dc944abba237500c90bacd2208b4 Mon Sep 17 00:00:00 2001 From: Ilya Lakhin Date: Fri, 31 Jul 2020 12:08:14 +0700 Subject: [PATCH 34/64] Code cleanup --- src/fn_call.rs | 4 +- src/fn_native.rs | 2 +- src/fn_register.rs | 2 +- tests/closures.rs | 184 ++++++++++++++++++++++++--------------------- 4 files changed, 101 insertions(+), 91 deletions(-) diff --git a/src/fn_call.rs b/src/fn_call.rs index 6f82df42..dd56b88d 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -5,7 +5,7 @@ use crate::calc_fn_hash; use crate::engine::{ search_imports, search_namespace, search_scope_only, Engine, Imports, State, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_PRINT, - KEYWORD_TYPE_OF, KEYWORD_SHARED, KEYWORD_TAKE + KEYWORD_TYPE_OF, KEYWORD_SHARED, }; use crate::error::ParseErrorType; use crate::fn_native::{FnCallArgs, FnPtr}; @@ -31,7 +31,7 @@ use crate::parser::FLOAT; use crate::engine::{FN_IDX_GET, FN_IDX_SET}; #[cfg(not(feature = "no_object"))] -use crate::engine::{Map, Target, FN_GET, FN_SET}; +use crate::engine::{Map, Target, FN_GET, FN_SET, KEYWORD_TAKE}; use crate::stdlib::{ any::{type_name, TypeId}, diff --git a/src/fn_native.rs b/src/fn_native.rs index 54f3da08..de926a47 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -1,6 +1,6 @@ //! Module defining interfaces to native-Rust functions. -use crate::any::{Dynamic, Variant}; +use crate::any::Dynamic; use crate::calc_fn_hash; use crate::engine::Engine; use crate::module::Module; diff --git a/src/fn_register.rs b/src/fn_register.rs index eef4e66b..e01f44a1 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -15,7 +15,7 @@ use crate::stdlib::{ any::TypeId, boxed::Box, mem, - string::{String, ToString}, + string::String, }; /// Trait to register custom functions with the `Engine`. diff --git a/tests/closures.rs b/tests/closures.rs index 6198bc26..4f8c16dd 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -1,6 +1,12 @@ #![cfg(not(feature = "no_function"))] -use rhai::{Dynamic, Engine, EvalAltResult, RegisterFn, FnPtr, Module, INT, Array}; -use std::any::{TypeId, Any}; +use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, Module, INT}; +use std::any::TypeId; + +#[cfg(not(feature = "no_shared"))] +use rhai::RegisterFn; + +#[cfg(not(feature = "no_index"))] +use rhai::Array; #[test] fn test_fn_ptr_curry_call() -> Result<(), Box> { @@ -35,7 +41,7 @@ fn test_fn_ptr_curry_call() -> Result<(), Box> { } #[test] -#[cfg(not(feature = "no_capture"))] +#[cfg(all(not(feature = "no_capture"), not(feature = "no_object")))] fn test_closures() -> Result<(), Box> { let engine = Engine::new(); @@ -110,9 +116,11 @@ fn test_shared() -> Result<(), Box> { 'x' ); - assert_eq!( - engine.eval::( - r#" + #[cfg(not(feature = "no_index"))] + { + assert_eq!( + engine.eval::( + r#" let s = shared("test"); let i = shared(0); i = 2; @@ -120,12 +128,10 @@ fn test_shared() -> Result<(), Box> { s "# - )?, - "teSt" - ); + )?, + "teSt" + ); - #[cfg(not(feature = "no_index"))] - { assert_eq!( engine.eval::( r#" @@ -137,6 +143,7 @@ fn test_shared() -> Result<(), Box> { 5 ); + #[cfg(not(feature = "no_object"))] assert_eq!( engine.eval::( r" @@ -170,6 +177,7 @@ fn test_shared() -> Result<(), Box> { true ); + #[cfg(not(feature = "no_object"))] assert_eq!( engine.eval::( r#" @@ -250,95 +258,97 @@ fn test_shared() -> Result<(), Box> { 42 ); - #[derive(Clone)] - struct TestStruct { - x: INT, - } - - impl TestStruct { - fn update(&mut self) { - self.x += 1000; + #[cfg(not(feature = "no_object"))] + { + #[derive(Clone)] + struct TestStruct { + x: INT, } - fn merge(&mut self, other: Self) { - self.x += other.x; + impl TestStruct { + fn update(&mut self) { + self.x += 1000; + } + + fn merge(&mut self, other: Self) { + self.x += other.x; + } + + fn get_x(&mut self) -> INT { + self.x + } + + fn set_x(&mut self, new_x: INT) { + self.x = new_x; + } + + fn new() -> Self { + TestStruct { x: 1 } + } } - fn get_x(&mut self) -> INT { - self.x - } + engine + .register_type::() + .register_get_set("x", TestStruct::get_x, TestStruct::set_x) + .register_fn("update", TestStruct::update) + .register_fn("merge", TestStruct::merge) + .register_fn("new_ts", TestStruct::new) + .register_raw_fn( + "mutate_with_cb", + &[ + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + ], + move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { + let fp = std::mem::take(args[2]).cast::(); + let mut value = args[1].clone(); + { + let mut lock = value.write_lock::().unwrap(); + *lock = *lock + 1; + } + let this_ptr = args.get_mut(0).unwrap(); - fn set_x(&mut self, new_x: INT) { - self.x = new_x; - } + fp.call_dynamic(engine, lib, Some(this_ptr), [value]) + }, + ); - fn new() -> Self { - TestStruct { x: 1 } - } - } + assert_eq!( + engine.eval::( + r" + let a = shared(new_ts()); - engine.register_type::(); + a.x = 100; + a.update(); + a.merge(a.take()); // take is important to prevent a deadlock - engine.register_get_set("x", TestStruct::get_x, TestStruct::set_x); - engine.register_fn("update", TestStruct::update); - engine.register_fn("merge", TestStruct::merge); - engine.register_fn("new_ts", TestStruct::new); - engine. - register_raw_fn( - "mutate_with_cb", - &[ - TypeId::of::(), - TypeId::of::(), - TypeId::of::(), - ], - move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { - let fp = std::mem::take(args[2]).cast::(); - let mut value = args[1].clone(); - { - let mut lock = value.write_lock::().unwrap(); - *lock = *lock + 1; - } - let this_ptr = args.get_mut(0).unwrap(); - - fp.call_dynamic(engine, lib, Some(this_ptr), [value]) - }, + a.x + " + )?, + 2200 ); - assert_eq!( - engine.eval::( - r" - let a = shared(new_ts()); + assert_eq!( + engine.eval::( + r" + let a = shared(new_ts()); + let b = shared(100); - a.x = 100; - a.update(); - a.merge(a.take()); // take is important to prevent a deadlock + a.mutate_with_cb(b, |param| { + this.x = param; + param = 50; + this.update(); + }); - a.x - " - )?, - 2200 - ); + a.update(); + a.x += b; - assert_eq!( - engine.eval::( - r" - let a = shared(new_ts()); - let b = shared(100); - - a.mutate_with_cb(b, |param| { - this.x = param; - param = 50; - this.update(); - }); - - a.update(); - a.x += b; - - a.x - " - )?, - 2151 - ); + a.x + " + )?, + 2151 + ); + } Ok(()) } From 5d1f5cc2b41286b03fae08ae2cf11c8fef3527c8 Mon Sep 17 00:00:00 2001 From: Ilya Lakhin Date: Fri, 31 Jul 2020 13:10:05 +0700 Subject: [PATCH 35/64] Dynamic::read renamed to Dynamic::clone_inner_data --- src/any.rs | 88 +++++++++++++++++++++++++------------------------- src/fn_call.rs | 2 +- src/scope.rs | 2 +- 3 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/any.rs b/src/any.rs index 1a82d237..cbb3e444 100644 --- a/src/any.rs +++ b/src/any.rs @@ -709,6 +709,46 @@ impl Dynamic { self.try_cast::().unwrap() } + /// Get a copy of a specific type to the `Dynamic`. + /// Casting to `Dynamic` returns a clone of the value in case of NON-shared + /// Dynamic. In case of Shared Dynamic returns a clone of the inner data of + /// Shared Dynamic. + /// Returns `None` if the cast fails. + #[inline(always)] + pub fn clone_inner_data(&self) -> Option { + match self.0 { + Union::Shared(ref cell) => { + let type_id = TypeId::of::(); + + if type_id != TypeId::of::() && cell.value_type_id != type_id { + return None + } + + #[cfg(not(feature = "sync"))] + return Some(cell + .container + .borrow() + .deref() + .downcast_ref::() + .unwrap() + .clone()); + + #[cfg(feature = "sync")] + return Some(cell + .container + .read() + .unwrap() + .deref() + .downcast_ref::() + .unwrap() + .clone()); + }, + _ => { + self.downcast_ref().cloned() + } + } + } + /// Get a reference of a specific type to the `Dynamic`. /// Casting to `Dynamic` just returns a reference to it. /// Returns `None` if the cast fails. @@ -740,46 +780,6 @@ impl Dynamic { } } - /// Get a copy of a specific type to the `Dynamic`. - /// Casting to `Dynamic` returns a clone of the value in case of NON-shared - /// Dynamic. In case of Shared Dynamic returns a clone of the inner data of - /// Shared Dynamic. - /// Returns `None` if the cast fails. - #[inline(always)] - pub fn read(&self) -> Option { - match self.0 { - Union::Shared(ref cell) => { - let type_id = TypeId::of::(); - - if type_id != TypeId::of::() && cell.value_type_id != type_id { - return None - } - - #[cfg(not(feature = "sync"))] - return Some(cell - .container - .borrow() - .deref() - .downcast_ref::() - .unwrap() - .clone()); - - #[cfg(feature = "sync")] - return Some(cell - .container - .read() - .unwrap() - .deref() - .downcast_ref::() - .unwrap() - .clone()); - }, - _ => { - self.downcast_ref().cloned() - } - } - } - /// Get a mutable reference of a specific type to the `Dynamic`. /// Casting to `Dynamic` just returns a mutable reference to it. /// Returns `None` if the cast fails. @@ -964,7 +964,7 @@ impl Dynamic { pub fn as_int(&self) -> Result { match self.0 { Union::Int(n) => Ok(n), - Union::Shared(_) => self.read::().ok_or_else(|| self.type_name()), + Union::Shared(_) => self.clone_inner_data::().ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -975,7 +975,7 @@ impl Dynamic { pub fn as_float(&self) -> Result { match self.0 { Union::Float(n) => Ok(n), - Union::Shared(_) => self.read::().ok_or_else(|| self.type_name()), + Union::Shared(_) => self.clone_inner_data::().ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -985,7 +985,7 @@ impl Dynamic { pub fn as_bool(&self) -> Result { match self.0 { Union::Bool(b) => Ok(b), - Union::Shared(_) => self.read::().ok_or_else(|| self.type_name()), + Union::Shared(_) => self.clone_inner_data::().ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -995,7 +995,7 @@ impl Dynamic { pub fn as_char(&self) -> Result { match self.0 { Union::Char(n) => Ok(n), - Union::Shared(_) => self.read::().ok_or_else(|| self.type_name()), + Union::Shared(_) => self.clone_inner_data::().ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } diff --git a/src/fn_call.rs b/src/fn_call.rs index e0dd1184..bed49230 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -658,7 +658,7 @@ impl Engine { )) } else if _fn_name == KEYWORD_TAKE { // take call - return Ok((obj.read::().unwrap(), false)); + return Ok((obj.clone_inner_data::().unwrap(), false)); } else { #[cfg(not(feature = "no_object"))] let redirected; diff --git a/src/scope.rs b/src/scope.rs index 2fb3df21..b1b9a3b3 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -340,7 +340,7 @@ impl<'a> Scope<'a> { /// ``` pub fn get_value(&self, name: &str) -> Option { self.get_entry(name) - .and_then(|Entry { value, .. }| value.read::()) + .and_then(|Entry { value, .. }| value.clone_inner_data::()) } /// Update the value of the named entry. From 871fcb38be48153c3970d8968e2726560ddcb599 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 31 Jul 2020 16:03:08 +0800 Subject: [PATCH 36/64] Minor style changes and make sure no_shared works on all. --- .github/workflows/build.yml | 2 + Cargo.toml | 6 +- src/any.rs | 276 +++++++++++++++++++++--------------- src/engine.rs | 85 ++++++----- src/fn_call.rs | 8 +- src/fn_native.rs | 25 +++- src/token.rs | 12 +- 7 files changed, 246 insertions(+), 168 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1370e03e..cd10a5a6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,6 +29,8 @@ jobs: - "--features no_object" - "--features no_function" - "--features no_module" + - "--features no_capture" + - "--features no_shared" - "--features unicode-xid-ident" toolchain: [stable] experimental: [false] diff --git a/Cargo.toml b/Cargo.toml index 0cc0e20d..13ef119f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ smallvec = { version = "1.4.1", default-features = false } [features] #default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"] -default = [] +default = [ "no_shared"] plugins = [] unchecked = [] # unchecked arithmetic sync = [] # restrict to only types that implement Send + Sync @@ -34,13 +34,13 @@ no_index = [] # no arrays and indexing no_object = [] # no custom objects no_function = [] # no script-defined functions no_capture = [] # no automatic read/write binding of anonymous function's local variables to it's external context -no_shared = [] # no explicit shared() and take() functions in the script code +no_shared = [] # no shared values no_module = [] # no modules internals = [] # expose internal data structures unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. # compiling for no-std -no_std = [ "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ] +no_std = [ "no_shared", "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ] [profile.release] lto = "fat" diff --git a/src/any.rs b/src/any.rs index cbb3e444..470d86c7 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1,9 +1,12 @@ //! Helper module which defines the `Any` trait to to allow dynamic value handling. -use crate::fn_native::{FnPtr, SendSync, SharedMut}; +use crate::fn_native::{FnPtr, SendSync}; use crate::parser::{ImmutableString, INT}; use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast}; +#[cfg(not(feature = "no_shared"))] +use crate::fn_native::SharedMut; + #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; @@ -17,13 +20,18 @@ use crate::stdlib::{ any::{type_name, Any, TypeId}, boxed::Box, fmt, + ops::{Deref, DerefMut}, string::String, - ops::{DerefMut, Deref} }; +#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "sync"))] -use crate::stdlib::{rc::Rc, cell::{RefCell, Ref, RefMut}}; +use crate::stdlib::{ + cell::{Ref, RefCell, RefMut}, + rc::Rc, +}; +#[cfg(not(feature = "no_shared"))] #[cfg(feature = "sync")] use crate::stdlib::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; @@ -151,32 +159,39 @@ pub enum Union { Map(Box), FnPtr(Box), Variant(Box>), + #[cfg(not(feature = "no_shared"))] Shared(Box), } /// Internal Shared Dynamic representation. /// /// Created with `Dynamic::into_shared()`. +#[cfg(not(feature = "no_shared"))] #[derive(Clone)] pub struct SharedCell { value_type_id: TypeId, value_type_name: &'static str, - container: SharedMut + container: SharedMut, } -/// Dynamic's underlying `Variant` read guard that supports `Deref` for Variant's -/// reference reading. +/// Underlying `Variant` read guard for `Dynamic`. /// -/// This data structure provides transparent interoperability between normal -/// `Dynamic` types and Shared Dynamic references. +/// This data structure provides transparent interoperability between +/// normal `Dynamic` and shared Dynamic values. #[derive(Debug)] pub struct DynamicReadLock<'d, T: Variant + Clone>(DynamicReadLockInner<'d, T>); +/// Different types of read guards for `DynamicReadLock`. #[derive(Debug)] enum DynamicReadLockInner<'d, T: Variant + Clone> { + /// A simple reference to a non-shared value. Reference(&'d T), + /// A read guard to a shared `RefCell`. + #[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "sync"))] Guard(Ref<'d, Dynamic>), + /// A read guard to a shared `RwLock`. + #[cfg(not(feature = "no_shared"))] #[cfg(feature = "sync")] Guard(RwLockReadGuard<'d, Dynamic>), } @@ -188,25 +203,31 @@ impl<'d, T: Variant + Clone> Deref for DynamicReadLock<'d, T> { fn deref(&self) -> &Self::Target { match &self.0 { DynamicReadLockInner::Reference(reference) => reference.deref(), - // unwrapping is safe because all checks already done in it's constructor + // Unwrapping is safe because all checking is already done in its constructor + #[cfg(not(feature = "no_shared"))] DynamicReadLockInner::Guard(guard) => guard.downcast_ref().unwrap(), } } } -/// Dynamic's underlying `Variant` write guard that supports `Deref` and `DerefMut` -/// for Variant's reference reading/writing. +/// Underlying `Variant` write guard for `Dynamic`. /// -/// This data structure provides transparent interoperability between normal -/// `Dynamic` types and Shared Dynamic references. +/// This data structure provides transparent interoperability between +/// normal `Dynamic` and shared Dynamic values. #[derive(Debug)] pub struct DynamicWriteLock<'d, T: Variant + Clone>(DynamicWriteLockInner<'d, T>); +/// Different types of write guards for `DynamicReadLock`. #[derive(Debug)] enum DynamicWriteLockInner<'d, T: Variant + Clone> { + /// A simple mutable reference to a non-shared value. Reference(&'d mut T), + /// A write guard to a shared `RefCell`. + #[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "sync"))] Guard(RefMut<'d, Dynamic>), + /// A write guard to a shared `RwLock`. + #[cfg(not(feature = "no_shared"))] #[cfg(feature = "sync")] Guard(RwLockWriteGuard<'d, Dynamic>), } @@ -218,7 +239,8 @@ impl<'d, T: Variant + Clone> Deref for DynamicWriteLock<'d, T> { fn deref(&self) -> &Self::Target { match &self.0 { DynamicWriteLockInner::Reference(reference) => reference.deref(), - // unwrapping is safe because all checks already done in it's constructor + // Unwrapping is safe because all checking is already done in its constructor + #[cfg(not(feature = "no_shared"))] DynamicWriteLockInner::Guard(guard) => guard.downcast_ref().unwrap(), } } @@ -229,7 +251,8 @@ impl<'d, T: Variant + Clone> DerefMut for DynamicWriteLock<'d, T> { fn deref_mut(&mut self) -> &mut Self::Target { match &mut self.0 { DynamicWriteLockInner::Reference(reference) => reference.deref_mut(), - // unwrapping is safe because all checks already done in it's constructor + // Unwrapping is safe because all checking is already done in its constructor + #[cfg(not(feature = "no_shared"))] DynamicWriteLockInner::Guard(guard) => guard.downcast_mut().unwrap(), } } @@ -247,6 +270,7 @@ impl Dynamic { /// Does this `Dynamic` hold a shared data type /// instead of one of the support system primitive types? + #[cfg(not(feature = "no_shared"))] pub fn is_shared(&self) -> bool { match self.0 { Union::Shared(_) => true, @@ -284,6 +308,7 @@ impl Dynamic { Union::Map(_) => TypeId::of::(), Union::FnPtr(_) => TypeId::of::(), Union::Variant(value) => (***value).type_id(), + #[cfg(not(feature = "no_shared"))] Union::Shared(cell) => (**cell).value_type_id, } } @@ -307,6 +332,7 @@ impl Dynamic { #[cfg(not(feature = "no_std"))] Union::Variant(value) if value.is::() => "timestamp", Union::Variant(value) => (***value).type_name(), + #[cfg(not(feature = "no_shared"))] Union::Shared(cell) => (**cell).value_type_name, } } @@ -363,7 +389,8 @@ impl fmt::Display for Dynamic { #[cfg(not(feature = "no_std"))] Union::Variant(value) if value.is::() => write!(f, ""), Union::Variant(value) => write!(f, "{}", (*value).type_name()), - Union::Shared(cell) => write!(f, "{}", (**cell).value_type_name), + #[cfg(not(feature = "no_shared"))] + Union::Shared(cell) => write!(f, "", (**cell).value_type_name), } } } @@ -390,7 +417,8 @@ impl fmt::Debug for Dynamic { #[cfg(not(feature = "no_std"))] Union::Variant(value) if value.is::() => write!(f, ""), Union::Variant(value) => write!(f, "{}", (*value).type_name()), - Union::Shared(cell) => write!(f, "{}", (**cell).value_type_name), + #[cfg(not(feature = "no_shared"))] + Union::Shared(cell) => write!(f, "", (**cell).value_type_name), } } } @@ -411,7 +439,8 @@ impl Clone for Dynamic { Union::Map(ref value) => Self(Union::Map(value.clone())), Union::FnPtr(ref value) => Self(Union::FnPtr(value.clone())), Union::Variant(ref value) => (***value).clone_into_dynamic(), - Union::Shared(ref cell) => Self(Union::Shared(Box::new((**cell).clone()))) + #[cfg(not(feature = "no_shared"))] + Union::Shared(ref cell) => Self(Union::Shared(Box::new((**cell).clone()))), } } } @@ -523,53 +552,42 @@ impl Dynamic { Self(Union::Variant(Box::new(boxed))) } - /// Turns Dynamic into Shared Dynamic variant backed by runtime - /// mutable reference-counter container(`Arc>` or - /// `Rc` depending on `sync` feature). + /// Turn the `Dynamic` value into a shared `Dynamic` value backed by an `Rc>` + /// or `Arc>` depending on the `sync` feature. /// - /// Instances of Shared Dynamic are relatively cheap to clone. All clones of - /// Shared Dynamic references the same chunk of memory. + /// Shared `Dynamic` values are relatively cheap to clone as they simply increment the + /// reference counts. /// - /// The Dynamic is capable to work transparently with the it's inner - /// data, seamlessly casting between ordinary instances of Dynamic and - /// Shared Dynamic instances. + /// Shared `Dynamic` values can be converted seamlessly to and from ordinary `Dynamic` values. /// - /// If original value already a Shared variant returns identity. + /// If the `Dynamic` value is already shared, this method returns itself. + #[cfg(not(feature = "no_shared"))] pub fn into_shared(self) -> Self { match self.0 { Union::Shared(..) => self, - _ => { - let cell = SharedCell { - value_type_id: self.type_id(), - value_type_name: self.type_name(), - #[cfg(not(feature = "sync"))] - container: Rc::new(RefCell::new(self)), - #[cfg(feature = "sync")] - container: Arc::new(RwLock::new(self)), - }; + _ => Self(Union::Shared(Box::new(SharedCell { + value_type_id: self.type_id(), + value_type_name: self.type_name(), - Self(Union::Shared(Box::new(cell))) - }, + #[cfg(not(feature = "sync"))] + container: Rc::new(RefCell::new(self)), + #[cfg(feature = "sync")] + container: Arc::new(RwLock::new(self)), + }))), } } - /// Get a copy of the `Dynamic` value as a specific type. + /// Convert the `Dynamic` value into specific type. /// Casting to a `Dynamic` just returns as is. /// - /// Returns None if types mismatched. + /// Returns `None` if types mismatched. /// - /// # Shared Dynamic + /// # Panics and Deadlocks /// - /// When accessing Shared Dynamic in sync mode(`sync` feature enabled) - /// can block current thread while the underlined data is being written. + /// Under the `sync` feature, this call may deadlock. + /// Otherwise, this call panics if the data is currently borrowed for write. /// - /// When accessing Shared Dynamic in NON-sync mode can **panic** if the data - /// is currently borrowed for write. - /// - /// ## Safety - /// - /// Both situations normally shouldn't happen since most operations in Rhai - /// use pass-by-value data and the script executed in a single thread. + /// These normally shouldn't occur since most operations in Rhai is single-threaded. /// /// # Example /// @@ -588,20 +606,6 @@ impl Dynamic { return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v); } - #[cfg(not(feature = "sync"))] - if let Union::Shared(cell) = self.0 { - let reference = cell.container.borrow(); - - return (*reference).clone().try_cast() - } - - #[cfg(feature = "sync")] - if let Union::Shared(cell) = self.0 { - let read_lock = cell.container.read().unwrap(); - - return (*read_lock).clone().try_cast() - } - if type_id == TypeId::of::() { return match self.0 { Union::Int(value) => unsafe_try_cast(value), @@ -677,23 +681,35 @@ impl Dynamic { match self.0 { Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(), + + #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "sync"))] + Union::Shared(cell) => return cell.container.borrow().deref().clone().try_cast(), + + #[cfg(not(feature = "no_shared"))] + #[cfg(feature = "sync")] + Union::Shared(cell) => { + return cell.container.read().unwrap().deref().clone().try_cast() + } + _ => None, } } - /// Get a copy of the `Dynamic` value as a specific type. + /// Convert the `Dynamic` value into a specific type. /// Casting to a `Dynamic` just returns as is. /// - /// # Panics + /// Returns `None` if types mismatched. + /// + /// # Panics and Deadlocks /// /// Panics if the cast fails (e.g. the type of the actual value is not the - /// same as the specified type), or if the data held by Shared Dynamic is - /// currently borrowed(when the `sync` feature disabled). + /// same as the specified type). /// - /// # Notes + /// Under the `sync` feature, this call may deadlock. + /// Otherwise, this call panics if the data is currently borrowed for write. /// - /// If the `sync` feature enabled Shared Dynamic can block current thread - /// while the data being written. + /// These normally shouldn't occur since most operations in Rhai is single-threaded. /// /// # Example /// @@ -709,108 +725,115 @@ impl Dynamic { self.try_cast::().unwrap() } - /// Get a copy of a specific type to the `Dynamic`. - /// Casting to `Dynamic` returns a clone of the value in case of NON-shared - /// Dynamic. In case of Shared Dynamic returns a clone of the inner data of - /// Shared Dynamic. + /// Get a copy of the `Dynamic` as a specific type. + /// + /// If the `Dynamic` is not a shared value, it returns a cloned copy of the value. + /// + /// If the `Dynamic` is a shared value, it returns a cloned copy of the shared value. + /// /// Returns `None` if the cast fails. #[inline(always)] pub fn clone_inner_data(&self) -> Option { match self.0 { + #[cfg(not(feature = "no_shared"))] Union::Shared(ref cell) => { let type_id = TypeId::of::(); - if type_id != TypeId::of::() && cell.value_type_id != type_id { - return None + if type_id != TypeId::of::() && type_id != cell.value_type_id { + return None; } #[cfg(not(feature = "sync"))] - return Some(cell - .container - .borrow() - .deref() - .downcast_ref::() - .unwrap() - .clone()); + return Some( + cell.container + .borrow() + .deref() + .downcast_ref::() + .unwrap() + .clone(), + ); #[cfg(feature = "sync")] - return Some(cell - .container - .read() - .unwrap() - .deref() - .downcast_ref::() - .unwrap() - .clone()); - }, - _ => { - self.downcast_ref().cloned() + return Some( + cell.container + .read() + .unwrap() + .deref() + .downcast_ref::() + .unwrap() + .clone(), + ); } + _ => self.downcast_ref().cloned(), } } /// Get a reference of a specific type to the `Dynamic`. /// Casting to `Dynamic` just returns a reference to it. + /// /// Returns `None` if the cast fails. #[inline(always)] pub fn read_lock(&self) -> Option> { match self.0 { + #[cfg(not(feature = "no_shared"))] Union::Shared(ref cell) => { let type_id = TypeId::of::(); - if type_id != TypeId::of::() && cell.value_type_id != type_id { - return None + if type_id != TypeId::of::() && type_id != cell.value_type_id { + return None; } #[cfg(not(feature = "sync"))] return Some(DynamicReadLock(DynamicReadLockInner::Guard( - cell.container.borrow() + cell.container.borrow(), ))); #[cfg(feature = "sync")] return Some(DynamicReadLock(DynamicReadLockInner::Guard( - cell.container.read().unwrap() + cell.container.read().unwrap(), ))); - }, - _ => { - self.downcast_ref().map(|reference| { - DynamicReadLock(DynamicReadLockInner::Reference(reference)) - }) } + _ => self + .downcast_ref() + .map(|reference| DynamicReadLock(DynamicReadLockInner::Reference(reference))), } } /// Get a mutable reference of a specific type to the `Dynamic`. /// Casting to `Dynamic` just returns a mutable reference to it. + /// /// Returns `None` if the cast fails. #[inline(always)] pub fn write_lock(&mut self) -> Option> { match self.0 { + #[cfg(not(feature = "no_shared"))] Union::Shared(ref cell) => { let type_id = TypeId::of::(); if type_id != TypeId::of::() && cell.value_type_id != type_id { - return None + return None; } #[cfg(not(feature = "sync"))] return Some(DynamicWriteLock(DynamicWriteLockInner::Guard( - cell.container.borrow_mut() + cell.container.borrow_mut(), ))); #[cfg(feature = "sync")] return Some(DynamicWriteLock(DynamicWriteLockInner::Guard( - cell.container.write().unwrap() + cell.container.write().unwrap(), ))); - }, - _ => { - self.downcast_mut().map(|reference| { - DynamicWriteLock(DynamicWriteLockInner::Reference(reference)) - }) } + _ => self + .downcast_mut() + .map(|reference| DynamicWriteLock(DynamicWriteLockInner::Reference(reference))), } } + /// Get a reference of a specific type to the `Dynamic`. + /// Casting to `Dynamic` just returns a reference to it. + /// + /// Returns `None` if the cast fails. #[inline(always)] fn downcast_ref(&self) -> Option<&T> { let type_id = TypeId::of::(); @@ -884,10 +907,16 @@ impl Dynamic { match &self.0 { Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::(), + #[cfg(not(feature = "no_shared"))] + Union::Shared(_) => unreachable!(), _ => None, } } + /// Get a mutable reference of a specific type to the `Dynamic`. + /// Casting to `Dynamic` just returns a mutable reference to it. + /// + /// Returns `None` if the cast fails. #[inline(always)] fn downcast_mut(&mut self) -> Option<&mut T> { let type_id = TypeId::of::(); @@ -955,6 +984,8 @@ impl Dynamic { match &mut self.0 { Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::(), + #[cfg(not(feature = "no_shared"))] + Union::Shared(_) => unreachable!(), _ => None, } } @@ -964,7 +995,10 @@ impl Dynamic { pub fn as_int(&self) -> Result { match self.0 { Union::Int(n) => Ok(n), - Union::Shared(_) => self.clone_inner_data::().ok_or_else(|| self.type_name()), + #[cfg(not(feature = "no_shared"))] + Union::Shared(_) => self + .clone_inner_data::() + .ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -975,7 +1009,10 @@ impl Dynamic { pub fn as_float(&self) -> Result { match self.0 { Union::Float(n) => Ok(n), - Union::Shared(_) => self.clone_inner_data::().ok_or_else(|| self.type_name()), + #[cfg(not(feature = "no_shared"))] + Union::Shared(_) => self + .clone_inner_data::() + .ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -985,7 +1022,10 @@ impl Dynamic { pub fn as_bool(&self) -> Result { match self.0 { Union::Bool(b) => Ok(b), - Union::Shared(_) => self.clone_inner_data::().ok_or_else(|| self.type_name()), + #[cfg(not(feature = "no_shared"))] + Union::Shared(_) => self + .clone_inner_data::() + .ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -995,7 +1035,10 @@ impl Dynamic { pub fn as_char(&self) -> Result { match self.0 { Union::Char(n) => Ok(n), - Union::Shared(_) => self.clone_inner_data::().ok_or_else(|| self.type_name()), + #[cfg(not(feature = "no_shared"))] + Union::Shared(_) => self + .clone_inner_data::() + .ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -1025,6 +1068,7 @@ impl Dynamic { match self.0 { Union::Str(s) => Ok(s), Union::FnPtr(f) => Ok(f.take_data().0), + #[cfg(not(feature = "no_shared"))] Union::Shared(cell) => { #[cfg(not(feature = "sync"))] match &cell.container.borrow().deref().0 { diff --git a/src/engine.rs b/src/engine.rs index e083f84d..e3f5dad8 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,6 +1,6 @@ //! Main module defining the script evaluation `Engine`. -use crate::any::{map_std_type_name, Dynamic, Union, DynamicWriteLock}; +use crate::any::{map_std_type_name, Dynamic, DynamicWriteLock, Union}; use crate::calc_fn_hash; use crate::fn_call::run_builtin_op_assignment; use crate::fn_native::{CallableFunction, Callback, FnPtr}; @@ -37,9 +37,9 @@ use crate::stdlib::{ collections::{HashMap, HashSet}, fmt, format, iter::{empty, once}, + ops::DerefMut, string::{String, ToString}, vec::Vec, - ops::DerefMut, }; #[cfg(not(feature = "no_index"))] @@ -172,9 +172,9 @@ impl Target<'_> { /// Get the value of the `Target` as a `Dynamic`, cloning a referenced value if necessary. pub fn clone_into_dynamic(self) -> Dynamic { match self { - Self::Ref(r) => r.clone(), // Referenced value is cloned + Self::Ref(r) => r.clone(), // Referenced value is cloned Self::LockGuard((_, orig)) => orig, // Return original container of the Shared Dynamic - Self::Value(v) => v, // Owned value is simply taken + Self::Value(v) => v, // Owned value is simply taken #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, ch) => ch, // Character is taken } @@ -229,15 +229,17 @@ impl Target<'_> { #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] impl<'a> From<&'a mut Dynamic> for Target<'a> { fn from(value: &'a mut Dynamic) -> Self { + #[cfg(not(feature = "no_shared"))] if value.is_shared() { - // clone is cheap since it holds Arc/Rw under the hood + // cloning is cheap since it holds Arc/Rw under the hood let container = value.clone(); - Self::LockGuard((value.write_lock::().unwrap(), container)) - } else { - Self::Ref(value) + return Self::LockGuard((value.write_lock::().unwrap(), container)); } + + Self::Ref(value) } } + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] impl> From for Target<'_> { fn from(value: T) -> Self { @@ -706,7 +708,7 @@ impl Engine { // Try to call an index setter #[cfg(not(feature = "no_index"))] Ok(obj_ptr) if obj_ptr.is_value() => { - next = Some((1, _new_val.unwrap())); + next = Some((1, _new_val.unwrap())); } // Indexed value is a reference - update directly Ok(ref mut obj_ptr) => { @@ -740,13 +742,13 @@ impl Engine { state, lib, FN_IDX_SET, 0, args, is_ref, true, false, None, None, level, ) - .or_else(|err| match *err { - // If there is no index setter, no need to set it back because the indexer is read-only - EvalAltResult::ErrorFunctionNotFound(_, _) => { - Ok(Default::default()) - } - _ => Err(err), - })?; + .or_else(|err| match *err { + // If there is no index setter, no need to set it back because the indexer is read-only + EvalAltResult::ErrorFunctionNotFound(_, _) => { + Ok(Default::default()) + } + _ => Err(err), + })?; } // next step is custom index setter call in case of error @@ -759,7 +761,7 @@ impl Engine { )?; } None => (), - _ => unreachable!() + _ => unreachable!(), } Ok(Default::default()) @@ -881,8 +883,15 @@ impl Engine { let (result, may_be_changed) = self .eval_dot_index_chain_helper( - state, lib, this_ptr, &mut val.into(), expr, idx_values, next_chain, - level, _new_val, + state, + lib, + this_ptr, + &mut val.into(), + expr, + idx_values, + next_chain, + level, + _new_val, ) .map_err(|err| err.new_position(*pos))?; @@ -1318,11 +1327,13 @@ impl Engine { )), // Normal assignment ScopeEntryType::Normal if op.is_empty() => { - if lhs_ptr.is_shared() { - *lhs_ptr.write_lock::().unwrap() = rhs_val; + #[cfg(not(feature = "no_shared"))] + let lhs_ptr = if lhs_ptr.is_shared() { + lhs_ptr.write_lock::().unwrap(); } else { - *lhs_ptr = rhs_val; - } + lhs_ptr + }; + *lhs_ptr = rhs_val; Ok(Default::default()) } // Op-assignment - in order of precedence: @@ -1340,13 +1351,15 @@ impl Engine { .get_fn(hash_fn, false) .or_else(|| self.packages.get_fn(hash_fn, false)) { - if lhs_ptr.is_shared() { - // Overriding exact implementation - func(self, lib, &mut [&mut lhs_ptr.write_lock::().unwrap(), &mut rhs_val])?; + // Overriding exact implementation + #[cfg(not(feature = "no_shared"))] + let lhs_ptr = if lhs_ptr.is_shared() { + &mut lhs_ptr.write_lock::().unwrap() } else { - // Overriding exact implementation - func(self, lib, &mut [lhs_ptr, &mut rhs_val])?; - } + lhs_ptr + }; + + func(self, lib, &mut [lhs_ptr, &mut rhs_val])?; } else if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_none() { // Not built in, map to `var = var op rhs` let op = &op[..op.len() - 1]; // extract operator without = @@ -1360,13 +1373,15 @@ impl Engine { state, lib, op, 0, args, false, false, false, None, None, level, ) .map_err(|err| err.new_position(*op_pos))?; - if lhs_ptr.is_shared() { - // Set value to LHS - *lhs_ptr.write_lock::().unwrap() = value; + + #[cfg(not(feature = "no_shared"))] + let lhs_ptr = if lhs_ptr.is_shared() { + lhs_ptr.write_lock::().unwrap() } else { - // Set value to LHS - *lhs_ptr = value; - } + lhs_ptr + }; + + *lhs_ptr = value; } Ok(Default::default()) } diff --git a/src/fn_call.rs b/src/fn_call.rs index bed49230..d093c165 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -5,7 +5,7 @@ use crate::calc_fn_hash; use crate::engine::{ search_imports, search_namespace, search_scope_only, Engine, Imports, State, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_PRINT, - KEYWORD_TYPE_OF, KEYWORD_SHARED, + KEYWORD_TYPE_OF, }; use crate::error::ParseErrorType; use crate::fn_native::{FnCallArgs, FnPtr}; @@ -14,9 +14,9 @@ use crate::optimize::OptimizationLevel; use crate::parser::{Expr, ImmutableString, AST, INT}; use crate::result::EvalAltResult; use crate::scope::Scope; +use crate::stdlib::ops::Deref; use crate::token::Position; use crate::utils::StaticVec; -use crate::stdlib::ops::Deref; #[cfg(not(feature = "no_function"))] use crate::{ @@ -34,6 +34,9 @@ use crate::engine::{FN_IDX_GET, FN_IDX_SET}; #[cfg(not(feature = "no_object"))] use crate::engine::{Map, Target, FN_GET, FN_SET, KEYWORD_TAKE}; +#[cfg(not(feature = "no_shared"))] +use crate::engine::KEYWORD_SHARED; + use crate::stdlib::{ any::{type_name, TypeId}, boxed::Box, @@ -771,6 +774,7 @@ impl Engine { } // Handle shared() + #[cfg(not(feature = "no_shared"))] if name == KEYWORD_SHARED && args_expr.len() == 1 { let expr = args_expr.get(0).unwrap(); let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; diff --git a/src/fn_native.rs b/src/fn_native.rs index f1e3a63c..f8bb0bf1 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -18,33 +18,46 @@ use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, iter::empty, string::Stri use crate::stdlib::mem; #[cfg(not(feature = "sync"))] -use crate::stdlib::{rc::Rc, cell::RefCell}; +use crate::stdlib::rc::Rc; #[cfg(feature = "sync")] -use crate::stdlib::sync::{Arc, RwLock}; +use crate::stdlib::sync::Arc; + +#[cfg(not(feature = "no_shared"))] +#[cfg(not(feature = "sync"))] +use crate::stdlib::cell::RefCell; +#[cfg(not(feature = "no_shared"))] +#[cfg(feature = "sync")] +use crate::stdlib::sync::RwLock; /// Trait that maps to `Send + Sync` only under the `sync` feature. #[cfg(feature = "sync")] pub trait SendSync: Send + Sync {} +/// Trait that maps to `Send + Sync` only under the `sync` feature. #[cfg(feature = "sync")] impl SendSync for T {} /// Trait that maps to `Send + Sync` only under the `sync` feature. #[cfg(not(feature = "sync"))] pub trait SendSync {} +/// Trait that maps to `Send + Sync` only under the `sync` feature. #[cfg(not(feature = "sync"))] impl SendSync for T {} -/// Immutable reference counting container +/// Immutable reference-counted container #[cfg(not(feature = "sync"))] pub type Shared = Rc; +/// Immutable reference-counted container #[cfg(feature = "sync")] pub type Shared = Arc; -/// Mutable reference counting container(read-write lock) +/// Mutable reference-counted container (read-write lock) +#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "sync"))] -pub type SharedMut = Rc>; +pub type SharedMut = Shared>; +/// Mutable reference-counted container (read-write lock) +#[cfg(not(feature = "no_shared"))] #[cfg(feature = "sync")] -pub type SharedMut = Arc>; +pub type SharedMut = Shared>; /// Consume a `Shared` resource and return a mutable reference to the wrapped value. /// If the resource is shared (i.e. has other outstanding references), a cloned copy is used. diff --git a/src/token.rs b/src/token.rs index 367a1dcc..f5a160e3 100644 --- a/src/token.rs +++ b/src/token.rs @@ -2,7 +2,7 @@ use crate::engine::{ Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, - KEYWORD_SHARED, KEYWORD_TAKE, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF, + KEYWORD_PRINT, KEYWORD_SHARED, KEYWORD_TAKE, KEYWORD_THIS, KEYWORD_TYPE_OF, }; use crate::error::LexError; @@ -503,12 +503,12 @@ impl Token { "===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "public" | "new" | "use" | "module" | "package" | "var" | "static" | "with" | "do" | "each" | "then" | "goto" | "exit" | "switch" | "match" | "case" | "try" | "catch" - | "default" | "void" | "null" | "nil" | "spawn" | "go" | "sync" - | "async" | "await" | "yield" => Reserved(syntax.into()), + | "default" | "void" | "null" | "nil" | "spawn" | "go" | "sync" | "async" | "await" + | "yield" => Reserved(syntax.into()), KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR - | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_SHARED - | KEYWORD_TAKE |KEYWORD_THIS => Reserved(syntax.into()), + | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_SHARED | KEYWORD_TAKE + | KEYWORD_THIS => Reserved(syntax.into()), _ => return None, }) @@ -1439,7 +1439,7 @@ pub fn is_keyword_function(name: &str) -> bool { || name == KEYWORD_FN_PTR_CALL || name == KEYWORD_FN_PTR_CURRY; - #[cfg(not(feature = "no-shared"))] + #[cfg(not(feature = "no_shared"))] { result = result || name == KEYWORD_SHARED || name == KEYWORD_TAKE; } From e70e0ff4e2ef97a9258977a18e15f8aafa7cfd2c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 31 Jul 2020 16:39:38 +0800 Subject: [PATCH 37/64] Fixup. --- Cargo.toml | 2 +- src/any.rs | 33 ++++-- src/engine.rs | 34 ++++-- tests/closures.rs | 294 ---------------------------------------------- tests/shared.rs | 268 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 310 insertions(+), 321 deletions(-) create mode 100644 tests/shared.rs diff --git a/Cargo.toml b/Cargo.toml index 13ef119f..c450eecd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ smallvec = { version = "1.4.1", default-features = false } [features] #default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"] -default = [ "no_shared"] +default = [] plugins = [] unchecked = [] # unchecked arithmetic sync = [] # restrict to only types that implement Send + Sync diff --git a/src/any.rs b/src/any.rs index 470d86c7..1f1a6df8 100644 --- a/src/any.rs +++ b/src/any.rs @@ -578,7 +578,9 @@ impl Dynamic { } /// Convert the `Dynamic` value into specific type. - /// Casting to a `Dynamic` just returns as is. + /// + /// Casting to a `Dynamic` just returns as is, but if it contains a shared value, + /// it is cloned into a `Dynamic` with a normal value. /// /// Returns `None` if types mismatched. /// @@ -602,6 +604,19 @@ impl Dynamic { pub fn try_cast(self) -> Option { let type_id = TypeId::of::(); + match self.0 { + #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "sync"))] + Union::Shared(cell) => return cell.container.borrow().deref().clone().try_cast(), + + #[cfg(not(feature = "no_shared"))] + #[cfg(feature = "sync")] + Union::Shared(cell) => { + return cell.container.read().unwrap().deref().clone().try_cast() + } + _ => (), + } + if type_id == TypeId::of::() { return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v); } @@ -681,23 +696,15 @@ impl Dynamic { match self.0 { Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(), - - #[cfg(not(feature = "no_shared"))] - #[cfg(not(feature = "sync"))] - Union::Shared(cell) => return cell.container.borrow().deref().clone().try_cast(), - - #[cfg(not(feature = "no_shared"))] - #[cfg(feature = "sync")] - Union::Shared(cell) => { - return cell.container.read().unwrap().deref().clone().try_cast() - } - + Union::Shared(_) => unreachable!(), _ => None, } } /// Convert the `Dynamic` value into a specific type. - /// Casting to a `Dynamic` just returns as is. + /// + /// Casting to a `Dynamic` just returns as is, but if it contains a shared value, + /// it is cloned into a `Dynamic` with a normal value. /// /// Returns `None` if types mismatched. /// diff --git a/src/engine.rs b/src/engine.rs index e3f5dad8..b3585804 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1328,12 +1328,15 @@ impl Engine { // Normal assignment ScopeEntryType::Normal if op.is_empty() => { #[cfg(not(feature = "no_shared"))] - let lhs_ptr = if lhs_ptr.is_shared() { - lhs_ptr.write_lock::().unwrap(); + if lhs_ptr.is_shared() { + *lhs_ptr.write_lock::().unwrap() = rhs_val; } else { - lhs_ptr - }; - *lhs_ptr = rhs_val; + *lhs_ptr = rhs_val; + } + #[cfg(feature = "no_shared")] + { + *lhs_ptr = rhs_val; + } Ok(Default::default()) } // Op-assignment - in order of precedence: @@ -1351,14 +1354,17 @@ impl Engine { .get_fn(hash_fn, false) .or_else(|| self.packages.get_fn(hash_fn, false)) { - // Overriding exact implementation + let mut lock_guard; + #[cfg(not(feature = "no_shared"))] let lhs_ptr = if lhs_ptr.is_shared() { - &mut lhs_ptr.write_lock::().unwrap() + lock_guard = Some(lhs_ptr.write_lock::().unwrap()); + lock_guard.as_deref_mut().unwrap() } else { lhs_ptr }; + // Overriding exact implementation func(self, lib, &mut [lhs_ptr, &mut rhs_val])?; } else if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_none() { // Not built in, map to `var = var op rhs` @@ -1375,13 +1381,15 @@ impl Engine { .map_err(|err| err.new_position(*op_pos))?; #[cfg(not(feature = "no_shared"))] - let lhs_ptr = if lhs_ptr.is_shared() { - lhs_ptr.write_lock::().unwrap() + if lhs_ptr.is_shared() { + *lhs_ptr.write_lock::().unwrap() = value; } else { - lhs_ptr - }; - - *lhs_ptr = value; + *lhs_ptr = value; + } + #[cfg(feature = "no_shared")] + { + *lhs_ptr = value; + } } Ok(Default::default()) } diff --git a/tests/closures.rs b/tests/closures.rs index 4f8c16dd..c5282870 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -2,12 +2,6 @@ use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, Module, INT}; use std::any::TypeId; -#[cfg(not(feature = "no_shared"))] -use rhai::RegisterFn; - -#[cfg(not(feature = "no_index"))] -use rhai::Array; - #[test] fn test_fn_ptr_curry_call() -> Result<(), Box> { let mut module = Module::new(); @@ -64,291 +58,3 @@ fn test_closures() -> Result<(), Box> { Ok(()) } - -#[test] -#[cfg(not(feature = "no_shared"))] -fn test_shared() -> Result<(), Box> { - let mut engine = Engine::new(); - - assert_eq!( - engine.eval::( - r#" - shared(42) - "# - )?, - 42 - ); - - assert_eq!( - engine.eval::( - r#" - shared(true) - "# - )?, - true - ); - - #[cfg(not(feature = "no_float"))] - assert_eq!( - engine.eval::( - r#" - shared(4.2) - "# - )?, - 4.2 - ); - - assert_eq!( - engine.eval::( - r#" - shared("test") - "# - )?, - "test" - ); - - assert_eq!( - engine.eval::( - r#" - shared('x') - "# - )?, - 'x' - ); - - #[cfg(not(feature = "no_index"))] - { - assert_eq!( - engine.eval::( - r#" - let s = shared("test"); - let i = shared(0); - i = 2; - s[i] = 'S'; - - s - "# - )?, - "teSt" - ); - - assert_eq!( - engine.eval::( - r#" - let x = shared([1, 2, 3]); - let y = shared([4, 5]); - x + y - "# - )?.len(), - 5 - ); - - #[cfg(not(feature = "no_object"))] - assert_eq!( - engine.eval::( - r" - let x = shared([2, 9]); - x.insert(-1, 1); - x.insert(999, 3); - - let r = x.remove(2); - - let y = shared([4, 5]); - x.append(y); - - x.len + r - " - )?, - 14 - ); - - assert_eq!( - engine.eval::( - r#" - let x = shared([1, 2, 3]); - - if x[0] + x[2] == 4 { - true - } else { - false - } - "# - )?, - true - ); - - #[cfg(not(feature = "no_object"))] - assert_eq!( - engine.eval::( - r#" - let x = shared([1, 2, 3]); - let y = shared(()); - - (|| { - for i in x { - y = i * 10; - } - }).call(); - - y - "# - )?, - 30 - ); - } - - #[cfg(not(feature = "no_object"))] - assert_eq!( - engine.eval::(r#" - let y = shared(#{a: 1, b: 2, c: 3}); - y.c = shared(5); - y.c - "#)?, - 5 - ); - - #[cfg(not(feature = "no_object"))] - assert_eq!( - engine.eval::(r#" - let y = shared(#{a: 1, b: 2, c: shared(3)}); - let c = y.c; - c = 5;// "c" holds Dynamic Shared - y.c - "#)?, - 5 - ); - - #[cfg(not(feature = "no_capture"))] - assert_eq!( - engine.eval::(r#" - let x = shared(1); - (|| x = x + 41).call(); - x - "#)?, - 42 - ); - - #[cfg(all(not(feature = "no_object"), not(feature = "no_capture")))] - assert_eq!( - engine.eval::(r#" - let x = shared(#{a: 1, b: shared(2), c: 3}); - let a = x.a; - let b = x.b; - a = 100; // does not hold reference to x.a - b = 20; // does hold reference to x.b - - let f = |a| { - x.c = x.a + x.b + a; - }; - - f.call(21); - - x.c - "#)?, - 42 - ); - - // Register a binary function named `foo` - engine.register_fn("custom_addition", |x: INT, y: INT| x + y); - - assert_eq!( - engine.eval::(r#" - custom_addition(shared(20), shared(22)) - "#)?, - 42 - ); - - #[cfg(not(feature = "no_object"))] - { - #[derive(Clone)] - struct TestStruct { - x: INT, - } - - impl TestStruct { - fn update(&mut self) { - self.x += 1000; - } - - fn merge(&mut self, other: Self) { - self.x += other.x; - } - - fn get_x(&mut self) -> INT { - self.x - } - - fn set_x(&mut self, new_x: INT) { - self.x = new_x; - } - - fn new() -> Self { - TestStruct { x: 1 } - } - } - - engine - .register_type::() - .register_get_set("x", TestStruct::get_x, TestStruct::set_x) - .register_fn("update", TestStruct::update) - .register_fn("merge", TestStruct::merge) - .register_fn("new_ts", TestStruct::new) - .register_raw_fn( - "mutate_with_cb", - &[ - TypeId::of::(), - TypeId::of::(), - TypeId::of::(), - ], - move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { - let fp = std::mem::take(args[2]).cast::(); - let mut value = args[1].clone(); - { - let mut lock = value.write_lock::().unwrap(); - *lock = *lock + 1; - } - let this_ptr = args.get_mut(0).unwrap(); - - fp.call_dynamic(engine, lib, Some(this_ptr), [value]) - }, - ); - - assert_eq!( - engine.eval::( - r" - let a = shared(new_ts()); - - a.x = 100; - a.update(); - a.merge(a.take()); // take is important to prevent a deadlock - - a.x - " - )?, - 2200 - ); - - assert_eq!( - engine.eval::( - r" - let a = shared(new_ts()); - let b = shared(100); - - a.mutate_with_cb(b, |param| { - this.x = param; - param = 50; - this.update(); - }); - - a.update(); - a.x += b; - - a.x - " - )?, - 2151 - ); - } - - Ok(()) -} diff --git a/tests/shared.rs b/tests/shared.rs new file mode 100644 index 00000000..e35de3d0 --- /dev/null +++ b/tests/shared.rs @@ -0,0 +1,268 @@ +#![cfg(not(feature = "no_shared"))] + +use rhai::{Array, Dynamic, Engine, EvalAltResult, FnPtr, Module, RegisterFn, INT}; +use std::any::TypeId; + +#[test] +fn test_shared() -> Result<(), Box> { + let mut engine = Engine::new(); + + assert_eq!(engine.eval::("shared(42)")?, 42); + + assert_eq!(engine.eval::("shared(true)")?, true); + + #[cfg(not(feature = "no_float"))] + assert_eq!(engine.eval::("shared(4.2)")?, 4.2); + + assert_eq!(engine.eval::(r#"shared("test")"#)?, "test"); + + assert_eq!(engine.eval::("shared('x')")?, 'x'); + + #[cfg(not(feature = "no_index"))] + { + assert_eq!( + engine.eval::( + r#" + let s = shared("test"); + let i = shared(0); + i = 2; + s[i] = 'S'; + + s + "# + )?, + "teSt" + ); + + assert_eq!( + engine + .eval::( + r#" + let x = shared([1, 2, 3]); + let y = shared([4, 5]); + x + y + "# + )? + .len(), + 5 + ); + + #[cfg(not(feature = "no_object"))] + assert_eq!( + engine.eval::( + r" + let x = shared([2, 9]); + x.insert(-1, 1); + x.insert(999, 3); + + let r = x.remove(2); + + let y = shared([4, 5]); + x.append(y); + + x.len + r + " + )?, + 14 + ); + + assert_eq!( + engine.eval::( + r#" + let x = shared([1, 2, 3]); + + if x[0] + x[2] == 4 { + true + } else { + false + } + "# + )?, + true + ); + + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "no_object"))] + assert_eq!( + engine.eval::( + r#" + let x = shared([1, 2, 3]); + let y = shared(()); + + (|| { + for i in x { + y = i * 10; + } + }).call(); + + y + "# + )?, + 30 + ); + } + + #[cfg(not(feature = "no_object"))] + assert_eq!( + engine.eval::( + r#" + let y = shared(#{a: 1, b: 2, c: 3}); + y.c = shared(5); + y.c + "# + )?, + 5 + ); + + #[cfg(not(feature = "no_object"))] + assert_eq!( + engine.eval::( + r#" + let y = shared(#{a: 1, b: 2, c: shared(3)}); + let c = y.c; + c = 5;// "c" holds Dynamic Shared + y.c + "# + )?, + 5 + ); + + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "no_capture"))] + assert_eq!( + engine.eval::( + r#" + let x = shared(1); + (|| x = x + 41).call(); + x + "# + )?, + 42 + ); + + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "no_object"))] + #[cfg(not(feature = "no_capture"))] + assert_eq!( + engine.eval::( + r#" + let x = shared(#{a: 1, b: shared(2), c: 3}); + let a = x.a; + let b = x.b; + a = 100; // does not hold reference to x.a + b = 20; // does hold reference to x.b + + let f = |a| { + x.c = x.a + x.b + a; + }; + + f.call(21); + + x.c + "# + )?, + 42 + ); + + // Register a binary function named `foo` + engine.register_fn("custom_addition", |x: INT, y: INT| x + y); + + assert_eq!( + engine.eval::("custom_addition(shared(20), shared(22))")?, + 42 + ); + + #[cfg(not(feature = "no_object"))] + { + #[derive(Clone)] + struct TestStruct { + x: INT, + } + + impl TestStruct { + fn update(&mut self) { + self.x += 1000; + } + + fn merge(&mut self, other: Self) { + self.x += other.x; + } + + fn get_x(&mut self) -> INT { + self.x + } + + fn set_x(&mut self, new_x: INT) { + self.x = new_x; + } + + fn new() -> Self { + TestStruct { x: 1 } + } + } + + engine + .register_type::() + .register_get_set("x", TestStruct::get_x, TestStruct::set_x) + .register_fn("update", TestStruct::update) + .register_fn("merge", TestStruct::merge) + .register_fn("new_ts", TestStruct::new) + .register_raw_fn( + "mutate_with_cb", + &[ + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + ], + move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { + let fp = std::mem::take(args[2]).cast::(); + let mut value = args[1].clone(); + { + let mut lock = value.write_lock::().unwrap(); + *lock = *lock + 1; + } + let this_ptr = args.get_mut(0).unwrap(); + + fp.call_dynamic(engine, lib, Some(this_ptr), [value]) + }, + ); + + assert_eq!( + engine.eval::( + r" + let a = shared(new_ts()); + + a.x = 100; + a.update(); + a.merge(a.take()); // take is important to prevent a deadlock + + a.x + " + )?, + 2200 + ); + + assert_eq!( + engine.eval::( + r" + let a = shared(new_ts()); + let b = shared(100); + + a.mutate_with_cb(b, |param| { + this.x = param; + param = 50; + this.update(); + }); + + a.update(); + a.x += b; + + a.x + " + )?, + 2151 + ); + } + + Ok(()) +} From df8587ac9136149735f6fc4625e33814c70705fc Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 31 Jul 2020 17:06:40 +0800 Subject: [PATCH 38/64] Make sure both shared and take can be called in both styles. --- src/fn_call.rs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/fn_call.rs b/src/fn_call.rs index d093c165..3d99d571 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -32,10 +32,10 @@ use crate::parser::FLOAT; use crate::engine::{FN_IDX_GET, FN_IDX_SET}; #[cfg(not(feature = "no_object"))] -use crate::engine::{Map, Target, FN_GET, FN_SET, KEYWORD_TAKE}; +use crate::engine::{Map, Target, FN_GET, FN_SET}; #[cfg(not(feature = "no_shared"))] -use crate::engine::KEYWORD_SHARED; +use crate::engine::{KEYWORD_SHARED, KEYWORD_TAKE}; use crate::stdlib::{ any::{type_name, TypeId}, @@ -659,9 +659,12 @@ impl Engine { .into(), false, )) - } else if _fn_name == KEYWORD_TAKE { + } else if _fn_name == KEYWORD_SHARED && idx.is_empty() { // take call - return Ok((obj.clone_inner_data::().unwrap(), false)); + Ok((obj.clone().into_shared(), false)) + } else if _fn_name == KEYWORD_TAKE && idx.is_empty() { + // take call + Ok((obj.clone_inner_data::().unwrap(), false)) } else { #[cfg(not(feature = "no_object"))] let redirected; @@ -782,6 +785,15 @@ impl Engine { return Ok(value.into_shared()); } + // Handle take() + #[cfg(not(feature = "no_shared"))] + if name == KEYWORD_TAKE && args_expr.len() == 1 { + let expr = args_expr.get(0).unwrap(); + let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + + return Ok(value.clone_inner_data::().unwrap()); + } + // Handle call() - Redirect function call let redirected; let mut args_expr = args_expr.as_ref(); From a35155b3e9ba9788d3e7fbc700ff751d2862be86 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 31 Jul 2020 17:07:09 +0800 Subject: [PATCH 39/64] Add shared and take to keywords list. --- doc/src/appendix/keywords.md | 61 ++++++++++++++-------------- doc/src/language/keywords.md | 31 +++++++------- doc/src/language/values-and-types.md | 3 +- doc/src/links.md | 1 + doc/src/start/features.md | 3 +- 5 files changed, 52 insertions(+), 47 deletions(-) diff --git a/doc/src/appendix/keywords.md b/doc/src/appendix/keywords.md index 5877f1a0..8aee03b1 100644 --- a/doc/src/appendix/keywords.md +++ b/doc/src/appendix/keywords.md @@ -3,35 +3,37 @@ Keywords List {{#include ../links.md}} -| Keyword | Description | Inactive under | -| :-------------------: | ---------------------------------------- | :-------------: | -| `true` | Boolean true literal | | -| `false` | Boolean false literal | | -| `let` | Variable declaration | | -| `const` | Constant declaration | | -| `if` | If statement | | -| `else` | else block of if statement | | -| `while` | While loop | | -| `loop` | Infinite loop | | -| `for` | For loop | | -| `in` | Containment test, part of for loop | | -| `continue` | Continue a loop at the next iteration | | -| `break` | Loop breaking | | -| `return` | Return value | | -| `throw` | Throw exception | | -| `import` | Import module | [`no_module`] | -| `export` | Export variable | [`no_module`] | -| `as` | Alias for variable export | [`no_module`] | -| `private` | Mark function private | [`no_function`] | -| `fn` (lower-case `f`) | Function definition | [`no_function`] | -| `Fn` (capital `F`) | Function to create a [function pointer] | | -| `call` | Call a [function pointer] | | -| `curry` | Curry a [function pointer] | | -| `this` | Reference to base object for method call | [`no_function`] | -| `type_of` | Get type name of value | | -| `print` | Print value | | -| `debug` | Print value in debug format | | -| `eval` | Evaluate script | | +| Keyword | Description | Inactive under | Overloadable | +| :-------------------: | ---------------------------------------- | :-------------: | :----------: | +| `true` | Boolean true literal | | No | +| `false` | Boolean false literal | | No | +| `let` | Variable declaration | | No | +| `const` | Constant declaration | | No | +| `shared` | Share value | [`no_shared`] | No | +| `take` | Un-share value | [`no_shared`] | No | +| `if` | If statement | | No | +| `else` | else block of if statement | | No | +| `while` | While loop | | No | +| `loop` | Infinite loop | | No | +| `for` | For loop | | No | +| `in` | Containment test, part of for loop | | No | +| `continue` | Continue a loop at the next iteration | | No | +| `break` | Loop breaking | | No | +| `return` | Return value | | No | +| `throw` | Throw exception | | No | +| `import` | Import module | [`no_module`] | No | +| `export` | Export variable | [`no_module`] | No | +| `as` | Alias for variable export | [`no_module`] | No | +| `private` | Mark function private | [`no_function`] | No | +| `fn` (lower-case `f`) | Function definition | [`no_function`] | No | +| `Fn` (capital `F`) | Function to create a [function pointer] | | Yes | +| `call` | Call a [function pointer] | | No | +| `curry` | Curry a [function pointer] | | No | +| `this` | Reference to base object for method call | [`no_function`] | No | +| `type_of` | Get type name of value | | Yes | +| `print` | Print value | | Yes | +| `debug` | Print value in debug format | | Yes | +| `eval` | Evaluate script | | Yes | Reserved Keywords @@ -59,7 +61,6 @@ Reserved Keywords | `package` | Package | | `spawn` | Threading | | `go` | Threading | -| `shared` | Threading | | `await` | Async | | `async` | Async | | `sync` | Async | diff --git a/doc/src/language/keywords.md b/doc/src/language/keywords.md index 509823db..a5142007 100644 --- a/doc/src/language/keywords.md +++ b/doc/src/language/keywords.md @@ -5,21 +5,22 @@ Keywords The following are reserved keywords in Rhai: -| Active keywords | Reserved keywords | Usage | Inactive under feature | -| ------------------------------------------------- | ---------------------------------------------------------- | --------------------- | :--------------------: | -| `true`, `false` | | Boolean constants | | -| `let`, `const` | `var`, `static` | Variable declarations | | -| `if`, `else` | `then`, `goto`, `exit` | Control flow | | -| | `switch`, `match`, `case` | Matching | | -| `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | Looping | | -| `fn`, `private` | `public`, `new` | Functions | [`no_function`] | -| `return` | | Return values | | -| `throw` | `try`, `catch` | Throw exceptions | | -| `import`, `export`, `as` | `use`, `with`, `module`, `package` | Modules/packages | [`no_module`] | -| `Fn`, `call`, `curry` | | Function pointers | | -| | `spawn`, `go`, `shared`, `sync`, `async`, `await`, `yield` | Threading/async | | -| `type_of`, `print`, `debug`, `eval` | | Special functions | | -| | `default`, `void`, `null`, `nil` | Special values | | +| Active keywords | Reserved keywords | Usage | Inactive under feature | +| ------------------------------------------------- | ------------------------------------------------ | --------------------- | :--------------------: | +| `true`, `false` | | Boolean constants | | +| `let`, `const` | `var`, `static` | Variable declarations | | +| `shared`, `take` | | Shared values | [`no_shared`] | +| `if`, `else` | `then`, `goto`, `exit` | Control flow | | +| | `switch`, `match`, `case` | Matching | | +| `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | Looping | | +| `fn`, `private` | `public`, `new` | Functions | [`no_function`] | +| `return` | | Return values | | +| `throw` | `try`, `catch` | Throw exceptions | | +| `import`, `export`, `as` | `use`, `with`, `module`, `package` | Modules/packages | [`no_module`] | +| `Fn`, `call`, `curry` | | Function pointers | | +| | `spawn`, `go`, `sync`, `async`, `await`, `yield` | Threading/async | | +| `type_of`, `print`, `debug`, `eval` | | Special functions | | +| | `default`, `void`, `null`, `nil` | Special values | | Keywords cannot become the name of a [function] or [variable], even when they are disabled. diff --git a/doc/src/language/values-and-types.md b/doc/src/language/values-and-types.md index 11426e84..833eead5 100644 --- a/doc/src/language/values-and-types.md +++ b/doc/src/language/values-and-types.md @@ -14,9 +14,10 @@ The following primitive types are supported natively: | **Immutable Unicode [string]** | `rhai::ImmutableString` (implemented as `Rc` or `Arc`) | `"string"` | `"hello"` etc. | | **[`Array`]** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` | | **[Object map]** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `"#{ "a": 1, "b": 2 }"` | -| **[Timestamp]** (implemented in the [`BasicTimePackage`][packages], disabled with [`no_std`]) | `std::time::Instant` ([`instant::Instant`] if [WASM] build) | `"timestamp"` | _not supported_ | +| **[Timestamp]** (implemented in the [`BasicTimePackage`][packages], disabled with [`no_std`]) | `std::time::Instant` ([`instant::Instant`] if [WASM] build) | `"timestamp"` | `""` | | **[Function pointer]** | `rhai::FnPtr` | `Fn` | `"Fn(foo)"` | | **[`Dynamic`] value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ | +| **Shared value** (a reference-counted, shared [`Dynamic`] value) | | _the actual type_ | _actual value_ | | **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. | | **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. | | **Nothing/void/nil/null/Unit** (or whatever it is called) | `()` | `"()"` | `""` _(empty string)_ | diff --git a/doc/src/links.md b/doc/src/links.md index 19fdf0e9..a22f0038 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -10,6 +10,7 @@ [`no_function`]: {{rootUrl}}/start/features.md [`no_module`]: {{rootUrl}}/start/features.md [`no_capture`]: {{rootUrl}}/start/features.md +[`no_shared`]: {{rootUrl}}/start/features.md [`no_std`]: {{rootUrl}}/start/features.md [`no-std`]: {{rootUrl}}/start/features.md [`internals`]: {{rootUrl}}/start/features.md diff --git a/doc/src/start/features.md b/doc/src/start/features.md index efeb39c5..9f0d42d4 100644 --- a/doc/src/start/features.md +++ b/doc/src/start/features.md @@ -24,7 +24,8 @@ more control over what a script can (or cannot) do. | `no_function` | Disable script-defined [functions]. | | `no_module` | Disable loading external [modules]. | | `no_capture` | Disable [capturing][capture] external variables in [anonymous functions] and [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls. | -| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | +| `no_shared` | Disable sharing of data values. | +| `no_std` | Build for `no-std` (implies `no_shared`). Notice that additional dependencies will be pulled in to replace `std` features. | | `serde` | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. | | `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. | | `unicode-xid-ident` | Allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) as identifiers. | From c8a60875ec5ee460b65203ae2f555541f3982835 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 31 Jul 2020 17:13:22 +0800 Subject: [PATCH 40/64] CI also in closures branch. --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cd10a5a6..276cbf72 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - closures pull_request: {} jobs: From d563b878aa8bb757f36ff003d6b865764f1ede45 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 31 Jul 2020 17:26:49 +0800 Subject: [PATCH 41/64] Fix no_std builds. --- src/any.rs | 1 + src/engine.rs | 1 + src/fn_call.rs | 19 +++++++++++++------ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/any.rs b/src/any.rs index 1f1a6df8..520bad0d 100644 --- a/src/any.rs +++ b/src/any.rs @@ -696,6 +696,7 @@ impl Dynamic { match self.0 { Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(), + #[cfg(not(feature = "no_shared"))] Union::Shared(_) => unreachable!(), _ => None, } diff --git a/src/engine.rs b/src/engine.rs index b3585804..5e8d88cf 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1354,6 +1354,7 @@ impl Engine { .get_fn(hash_fn, false) .or_else(|| self.packages.get_fn(hash_fn, false)) { + #[cfg(not(feature = "no_shared"))] let mut lock_guard; #[cfg(not(feature = "no_shared"))] diff --git a/src/fn_call.rs b/src/fn_call.rs index 3d99d571..6015d59f 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -5,7 +5,7 @@ use crate::calc_fn_hash; use crate::engine::{ search_imports, search_namespace, search_scope_only, Engine, Imports, State, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_PRINT, - KEYWORD_TYPE_OF, + KEYWORD_SHARED, KEYWORD_TAKE, KEYWORD_TYPE_OF, }; use crate::error::ParseErrorType; use crate::fn_native::{FnCallArgs, FnPtr}; @@ -34,9 +34,6 @@ use crate::engine::{FN_IDX_GET, FN_IDX_SET}; #[cfg(not(feature = "no_object"))] use crate::engine::{Map, Target, FN_GET, FN_SET}; -#[cfg(not(feature = "no_shared"))] -use crate::engine::{KEYWORD_SHARED, KEYWORD_TAKE}; - use crate::stdlib::{ any::{type_name, TypeId}, boxed::Box, @@ -661,10 +658,20 @@ impl Engine { )) } else if _fn_name == KEYWORD_SHARED && idx.is_empty() { // take call - Ok((obj.clone().into_shared(), false)) + #[cfg(not(feature = "no_shared"))] + { + Ok((obj.clone().into_shared(), false)) + } + #[cfg(feature = "no_shared")] + unreachable!() } else if _fn_name == KEYWORD_TAKE && idx.is_empty() { // take call - Ok((obj.clone_inner_data::().unwrap(), false)) + #[cfg(not(feature = "no_shared"))] + { + Ok((obj.clone_inner_data::().unwrap(), false)) + } + #[cfg(feature = "no_shared")] + unreachable!() } else { #[cfg(not(feature = "no_object"))] let redirected; From 0295e109ad56ea6b78c8d0c30674bdb968868207 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 31 Jul 2020 18:06:01 +0800 Subject: [PATCH 42/64] Fix features --- src/engine.rs | 22 +++++++++++++++++----- src/token.rs | 16 +++++++--------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 5e8d88cf..f9017d96 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,6 +1,6 @@ //! Main module defining the script evaluation `Engine`. -use crate::any::{map_std_type_name, Dynamic, DynamicWriteLock, Union}; +use crate::any::{map_std_type_name, Dynamic, Union}; use crate::calc_fn_hash; use crate::fn_call::run_builtin_op_assignment; use crate::fn_native::{CallableFunction, Callback, FnPtr}; @@ -31,13 +31,15 @@ use crate::module::resolvers; #[cfg(any(not(feature = "no_object"), not(feature = "no_module")))] use crate::utils::ImmutableString; +#[cfg(not(feature = "no_shared"))] +use crate::any::DynamicWriteLock; + use crate::stdlib::{ borrow::Cow, boxed::Box, collections::{HashMap, HashSet}, fmt, format, iter::{empty, once}, - ops::DerefMut, string::{String, ToString}, vec::Vec, }; @@ -45,6 +47,9 @@ use crate::stdlib::{ #[cfg(not(feature = "no_index"))] use crate::stdlib::any::TypeId; +#[cfg(not(feature = "no_shared"))] +use crate::stdlib::ops::DerefMut; + /// Variable-sized array of `Dynamic` values. /// /// Not available under the `no_index` feature. @@ -126,7 +131,8 @@ pub enum Target<'a> { /// The target is a mutable reference to a `Dynamic` value somewhere. Ref(&'a mut Dynamic), /// The target is a mutable reference to a Shared `Dynamic` value. - /// It holds the access guard and the original container both for cloning purposes + /// It holds both the access guard and the original shared value. + #[cfg(not(feature = "no_shared"))] LockGuard((DynamicWriteLock<'a, Dynamic>, Dynamic)), /// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects). Value(Dynamic), @@ -142,6 +148,7 @@ impl Target<'_> { pub fn is_ref(&self) -> bool { match self { Self::Ref(_) => true, + #[cfg(not(feature = "no_shared"))] Self::LockGuard(_) => true, Self::Value(_) => false, #[cfg(not(feature = "no_index"))] @@ -152,6 +159,7 @@ impl Target<'_> { pub fn is_value(&self) -> bool { match self { Self::Ref(_) => false, + #[cfg(not(feature = "no_shared"))] Self::LockGuard(_) => false, Self::Value(_) => true, #[cfg(not(feature = "no_index"))] @@ -163,6 +171,7 @@ impl Target<'_> { pub fn is(&self) -> bool { match self { Target::Ref(r) => r.is::(), + #[cfg(not(feature = "no_shared"))] Target::LockGuard((r, _)) => r.is::(), Target::Value(r) => r.is::(), #[cfg(not(feature = "no_index"))] @@ -173,7 +182,8 @@ impl Target<'_> { pub fn clone_into_dynamic(self) -> Dynamic { match self { Self::Ref(r) => r.clone(), // Referenced value is cloned - Self::LockGuard((_, orig)) => orig, // Return original container of the Shared Dynamic + #[cfg(not(feature = "no_shared"))] + Self::LockGuard((_, orig)) => orig, // Original value is simply taken Self::Value(v) => v, // Owned value is simply taken #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, ch) => ch, // Character is taken @@ -183,6 +193,7 @@ impl Target<'_> { pub fn as_mut(&mut self) -> &mut Dynamic { match self { Self::Ref(r) => *r, + #[cfg(not(feature = "no_shared"))] Self::LockGuard((r, _)) => r.deref_mut(), Self::Value(ref mut r) => r, #[cfg(not(feature = "no_index"))] @@ -194,6 +205,7 @@ impl Target<'_> { pub fn set_value(&mut self, new_val: Dynamic) -> Result<(), Box> { match self { Self::Ref(r) => **r = new_val, + #[cfg(not(feature = "no_shared"))] Self::LockGuard((r, _)) => **r = new_val, Self::Value(_) => { return Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS( @@ -231,7 +243,7 @@ impl<'a> From<&'a mut Dynamic> for Target<'a> { fn from(value: &'a mut Dynamic) -> Self { #[cfg(not(feature = "no_shared"))] if value.is_shared() { - // cloning is cheap since it holds Arc/Rw under the hood + // Cloning is cheap for a shared value let container = value.clone(); return Self::LockGuard((value.write_lock::().unwrap(), container)); } diff --git a/src/token.rs b/src/token.rs index f5a160e3..571ddf10 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1431,20 +1431,18 @@ fn get_identifier( /// Is this keyword allowed as a function? #[inline(always)] pub fn is_keyword_function(name: &str) -> bool { - let mut result = name == KEYWORD_PRINT + #[cfg(not(feature = "no_shared"))] + if name == KEYWORD_SHARED || name == KEYWORD_TAKE { + return true; + } + + name == KEYWORD_PRINT || name == KEYWORD_DEBUG || name == KEYWORD_TYPE_OF || name == KEYWORD_EVAL || name == KEYWORD_FN_PTR || name == KEYWORD_FN_PTR_CALL - || name == KEYWORD_FN_PTR_CURRY; - - #[cfg(not(feature = "no_shared"))] - { - result = result || name == KEYWORD_SHARED || name == KEYWORD_TAKE; - } - - result + || name == KEYWORD_FN_PTR_CURRY } /// Can this keyword be overridden as a function? From 8d0623d07f8b100f6e26d7489fbb7be9bbabf40c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 31 Jul 2020 18:43:34 +0800 Subject: [PATCH 43/64] Add is_shared function. --- doc/src/appendix/keywords.md | 1 + doc/src/language/keywords.md | 3 +-- src/engine.rs | 5 +++-- src/fn_call.rs | 19 +++++++++++++++++-- src/token.rs | 28 +++++++++++----------------- tests/shared.rs | 5 +++++ 6 files changed, 38 insertions(+), 23 deletions(-) diff --git a/doc/src/appendix/keywords.md b/doc/src/appendix/keywords.md index 8aee03b1..a1e20fed 100644 --- a/doc/src/appendix/keywords.md +++ b/doc/src/appendix/keywords.md @@ -9,6 +9,7 @@ Keywords List | `false` | Boolean false literal | | No | | `let` | Variable declaration | | No | | `const` | Constant declaration | | No | +| `is_shared` | Is a value shared? | | No | | `shared` | Share value | [`no_shared`] | No | | `take` | Un-share value | [`no_shared`] | No | | `if` | If statement | | No | diff --git a/doc/src/language/keywords.md b/doc/src/language/keywords.md index a5142007..0683386e 100644 --- a/doc/src/language/keywords.md +++ b/doc/src/language/keywords.md @@ -9,7 +9,7 @@ The following are reserved keywords in Rhai: | ------------------------------------------------- | ------------------------------------------------ | --------------------- | :--------------------: | | `true`, `false` | | Boolean constants | | | `let`, `const` | `var`, `static` | Variable declarations | | -| `shared`, `take` | | Shared values | [`no_shared`] | +| `shared`, `take`, `is_shared` | | Shared values | [`no_shared`] | | `if`, `else` | `then`, `goto`, `exit` | Control flow | | | | `switch`, `match`, `case` | Matching | | | `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | Looping | | @@ -23,4 +23,3 @@ The following are reserved keywords in Rhai: | | `default`, `void`, `null`, `nil` | Special values | | Keywords cannot become the name of a [function] or [variable], even when they are disabled. - diff --git a/src/engine.rs b/src/engine.rs index f9017d96..bd778e4e 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -99,6 +99,7 @@ pub const KEYWORD_FN_PTR_CALL: &str = "call"; pub const KEYWORD_FN_PTR_CURRY: &str = "curry"; pub const KEYWORD_SHARED: &str = "shared"; pub const KEYWORD_TAKE: &str = "take"; +pub const KEYWORD_IS_SHARED: &str = "is_shared"; pub const KEYWORD_THIS: &str = "this"; pub const FN_TO_STRING: &str = "to_string"; #[cfg(not(feature = "no_object"))] @@ -181,10 +182,10 @@ impl Target<'_> { /// Get the value of the `Target` as a `Dynamic`, cloning a referenced value if necessary. pub fn clone_into_dynamic(self) -> Dynamic { match self { - Self::Ref(r) => r.clone(), // Referenced value is cloned + Self::Ref(r) => r.clone(), // Referenced value is cloned #[cfg(not(feature = "no_shared"))] Self::LockGuard((_, orig)) => orig, // Original value is simply taken - Self::Value(v) => v, // Owned value is simply taken + Self::Value(v) => v, // Owned value is simply taken #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, ch) => ch, // Character is taken } diff --git a/src/fn_call.rs b/src/fn_call.rs index 6015d59f..d680800a 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -4,8 +4,8 @@ use crate::any::Dynamic; use crate::calc_fn_hash; use crate::engine::{ search_imports, search_namespace, search_scope_only, Engine, Imports, State, KEYWORD_DEBUG, - KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_PRINT, - KEYWORD_SHARED, KEYWORD_TAKE, KEYWORD_TYPE_OF, + KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_SHARED, + KEYWORD_PRINT, KEYWORD_SHARED, KEYWORD_TAKE, KEYWORD_TYPE_OF, }; use crate::error::ParseErrorType; use crate::fn_native::{FnCallArgs, FnPtr}; @@ -451,6 +451,12 @@ impl Engine { ))) } + // Fn + KEYWORD_IS_SHARED if args.len() == 1 => Err(Box::new(EvalAltResult::ErrorRuntime( + "'is_shared' should not be called in method style. Try is_shared(...);".into(), + Position::none(), + ))), + // eval - reaching this point it must be a method-style call KEYWORD_EVAL if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) => @@ -783,6 +789,15 @@ impl Engine { .into()); } + // Handle is_shared() + #[cfg(not(feature = "no_shared"))] + if name == KEYWORD_IS_SHARED && args_expr.len() == 1 { + let expr = args_expr.get(0).unwrap(); + let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + + return Ok(value.is_shared().into()); + } + // Handle shared() #[cfg(not(feature = "no_shared"))] if name == KEYWORD_SHARED && args_expr.len() == 1 { diff --git a/src/token.rs b/src/token.rs index 571ddf10..dfd50c27 100644 --- a/src/token.rs +++ b/src/token.rs @@ -2,7 +2,7 @@ use crate::engine::{ Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, - KEYWORD_PRINT, KEYWORD_SHARED, KEYWORD_TAKE, KEYWORD_THIS, KEYWORD_TYPE_OF, + KEYWORD_IS_SHARED, KEYWORD_PRINT, KEYWORD_SHARED, KEYWORD_TAKE, KEYWORD_THIS, KEYWORD_TYPE_OF, }; use crate::error::LexError; @@ -1431,28 +1431,22 @@ fn get_identifier( /// Is this keyword allowed as a function? #[inline(always)] pub fn is_keyword_function(name: &str) -> bool { - #[cfg(not(feature = "no_shared"))] - if name == KEYWORD_SHARED || name == KEYWORD_TAKE { - return true; + match name { + #[cfg(not(feature = "no_shared"))] + KEYWORD_SHARED | KEYWORD_TAKE | KEYWORD_IS_SHARED => true, + KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR + | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY => true, + _ => false, } - - name == KEYWORD_PRINT - || name == KEYWORD_DEBUG - || name == KEYWORD_TYPE_OF - || name == KEYWORD_EVAL - || name == KEYWORD_FN_PTR - || name == KEYWORD_FN_PTR_CALL - || name == KEYWORD_FN_PTR_CURRY } /// Can this keyword be overridden as a function? #[inline(always)] pub fn can_override_keyword(name: &str) -> bool { - name == KEYWORD_PRINT - || name == KEYWORD_DEBUG - || name == KEYWORD_TYPE_OF - || name == KEYWORD_EVAL - || name == KEYWORD_FN_PTR + match name { + KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR => true, + _ => false, + } } pub fn is_valid_identifier(name: impl Iterator) -> bool { diff --git a/tests/shared.rs b/tests/shared.rs index e35de3d0..7cd7af86 100644 --- a/tests/shared.rs +++ b/tests/shared.rs @@ -18,6 +18,11 @@ fn test_shared() -> Result<(), Box> { assert_eq!(engine.eval::("shared('x')")?, 'x'); + assert!(engine.eval::("is_shared(shared(42))")?); + + #[cfg(not(feature = "no_object"))] + assert!(engine.eval::("shared(42).is_shared()")?); + #[cfg(not(feature = "no_index"))] { assert_eq!( From 60891e694f480e7c62f7a6d06749958503ceaa3b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 31 Jul 2020 22:30:23 +0800 Subject: [PATCH 44/64] Streamline code and feature gates. --- src/any.rs | 17 ++- src/api.rs | 19 ++-- src/engine.rs | 69 +++++++----- src/fn_call.rs | 125 ++++++++++----------- src/optimize.rs | 23 ++-- src/packages/arithmetic.rs | 210 +++++++++++++---------------------- src/packages/array_basic.rs | 8 +- src/packages/iter_basic.rs | 18 ++- src/packages/logic.rs | 7 +- src/packages/map_basic.rs | 19 ++-- src/packages/math_basic.rs | 35 +++--- src/packages/string_basic.rs | 7 +- src/packages/string_more.rs | 7 +- src/parser.rs | 67 ++++++----- src/scope.rs | 55 ++++++++- src/token.rs | 4 +- 16 files changed, 337 insertions(+), 353 deletions(-) diff --git a/src/any.rs b/src/any.rs index 520bad0d..a8ee1e66 100644 --- a/src/any.rs +++ b/src/any.rs @@ -270,9 +270,9 @@ impl Dynamic { /// Does this `Dynamic` hold a shared data type /// instead of one of the support system primitive types? - #[cfg(not(feature = "no_shared"))] pub fn is_shared(&self) -> bool { match self.0 { + #[cfg(not(feature = "no_shared"))] Union::Shared(_) => true, _ => false, } @@ -440,7 +440,7 @@ impl Clone for Dynamic { Union::FnPtr(ref value) => Self(Union::FnPtr(value.clone())), Union::Variant(ref value) => (***value).clone_into_dynamic(), #[cfg(not(feature = "no_shared"))] - Union::Shared(ref cell) => Self(Union::Shared(Box::new((**cell).clone()))), + Union::Shared(ref cell) => Self(Union::Shared(cell.clone())), } } } @@ -561,9 +561,13 @@ impl Dynamic { /// Shared `Dynamic` values can be converted seamlessly to and from ordinary `Dynamic` values. /// /// If the `Dynamic` value is already shared, this method returns itself. - #[cfg(not(feature = "no_shared"))] + /// + /// # Panics + /// + /// Panics under the `no_shared` feature. pub fn into_shared(self) -> Self { - match self.0 { + #[cfg(not(feature = "no_shared"))] + return match self.0 { Union::Shared(..) => self, _ => Self(Union::Shared(Box::new(SharedCell { value_type_id: self.type_id(), @@ -574,7 +578,10 @@ impl Dynamic { #[cfg(feature = "sync")] container: Arc::new(RwLock::new(self)), }))), - } + }; + + #[cfg(feature = "no_shared")] + unimplemented!() } /// Convert the `Dynamic` value into specific type. diff --git a/src/api.rs b/src/api.rs index 42ff7037..e6e9ea7a 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1305,16 +1305,15 @@ impl Engine { mut ast: AST, optimization_level: OptimizationLevel, ) -> AST { - #[cfg(not(feature = "no_function"))] - let lib = ast - .lib() - .iter_fn() - .filter(|(_, _, _, f)| f.is_script()) - .map(|(_, _, _, f)| f.get_fn_def().clone()) - .collect(); - - #[cfg(feature = "no_function")] - let lib = Default::default(); + let lib = if cfg!(not(feature = "no_function")) { + ast.lib() + .iter_fn() + .filter(|(_, _, _, f)| f.is_script()) + .map(|(_, _, _, f)| f.get_fn_def().clone()) + .collect() + } else { + Default::default() + }; let stmt = mem::take(ast.statements_mut()); optimize_into_ast(self, scope, stmt, lib, optimization_level) diff --git a/src/engine.rs b/src/engine.rs index bd778e4e..cdd9c040 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -32,6 +32,7 @@ use crate::module::resolvers; use crate::utils::ImmutableString; #[cfg(not(feature = "no_shared"))] +#[cfg(not(feature = "no_object"))] use crate::any::DynamicWriteLock; use crate::stdlib::{ @@ -44,12 +45,13 @@ use crate::stdlib::{ vec::Vec, }; +#[cfg(not(feature = "no_shared"))] +#[cfg(not(feature = "no_object"))] +use crate::stdlib::ops::DerefMut; + #[cfg(not(feature = "no_index"))] use crate::stdlib::any::TypeId; -#[cfg(not(feature = "no_shared"))] -use crate::stdlib::ops::DerefMut; - /// Variable-sized array of `Dynamic` values. /// /// Not available under the `no_index` feature. @@ -134,6 +136,7 @@ pub enum Target<'a> { /// The target is a mutable reference to a Shared `Dynamic` value. /// It holds both the access guard and the original shared value. #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_object"))] LockGuard((DynamicWriteLock<'a, Dynamic>, Dynamic)), /// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects). Value(Dynamic), @@ -150,6 +153,7 @@ impl Target<'_> { match self { Self::Ref(_) => true, #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_object"))] Self::LockGuard(_) => true, Self::Value(_) => false, #[cfg(not(feature = "no_index"))] @@ -161,18 +165,32 @@ impl Target<'_> { match self { Self::Ref(_) => false, #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_object"))] Self::LockGuard(_) => false, Self::Value(_) => true, #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, _) => false, } } + /// Is the `Target` a shared value? + pub fn is_shared(&self) -> bool { + match self { + Self::Ref(r) => r.is_shared(), + #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_object"))] + Self::LockGuard(_) => true, + Self::Value(r) => r.is_shared(), + #[cfg(not(feature = "no_index"))] + Self::StringChar(_, _, _) => false, + } + } /// Is the `Target` a specific type? #[allow(dead_code)] pub fn is(&self) -> bool { match self { Target::Ref(r) => r.is::(), #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_object"))] Target::LockGuard((r, _)) => r.is::(), Target::Value(r) => r.is::(), #[cfg(not(feature = "no_index"))] @@ -184,6 +202,7 @@ impl Target<'_> { match self { Self::Ref(r) => r.clone(), // Referenced value is cloned #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_object"))] Self::LockGuard((_, orig)) => orig, // Original value is simply taken Self::Value(v) => v, // Owned value is simply taken #[cfg(not(feature = "no_index"))] @@ -195,6 +214,7 @@ impl Target<'_> { match self { Self::Ref(r) => *r, #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_object"))] Self::LockGuard((r, _)) => r.deref_mut(), Self::Value(ref mut r) => r, #[cfg(not(feature = "no_index"))] @@ -207,6 +227,7 @@ impl Target<'_> { match self { Self::Ref(r) => **r = new_val, #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_object"))] Self::LockGuard((r, _)) => **r = new_val, Self::Value(_) => { return Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS( @@ -243,6 +264,7 @@ impl Target<'_> { impl<'a> From<&'a mut Dynamic> for Target<'a> { fn from(value: &'a mut Dynamic) -> Self { #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_object"))] if value.is_shared() { // Cloning is cheap for a shared value let container = value.clone(); @@ -427,11 +449,11 @@ impl Default for Engine { progress: None, // optimization level - #[cfg(feature = "no_optimize")] - optimization_level: OptimizationLevel::None, - - #[cfg(not(feature = "no_optimize"))] - optimization_level: OptimizationLevel::Simple, + optimization_level: if cfg!(feature = "no_optimize") { + OptimizationLevel::None + } else { + OptimizationLevel::Simple + }, #[cfg(not(feature = "unchecked"))] limits: Limits { @@ -639,11 +661,11 @@ impl Engine { debug: Box::new(|_| {}), progress: None, - #[cfg(feature = "no_optimize")] - optimization_level: OptimizationLevel::None, - - #[cfg(not(feature = "no_optimize"))] - optimization_level: OptimizationLevel::Simple, + optimization_level: if cfg!(feature = "no_optimize") { + OptimizationLevel::None + } else { + OptimizationLevel::Simple + }, #[cfg(not(feature = "unchecked"))] limits: Limits { @@ -1340,16 +1362,11 @@ impl Engine { )), // Normal assignment ScopeEntryType::Normal if op.is_empty() => { - #[cfg(not(feature = "no_shared"))] - if lhs_ptr.is_shared() { + if cfg!(not(feature = "no_shared")) && lhs_ptr.is_shared() { *lhs_ptr.write_lock::().unwrap() = rhs_val; } else { *lhs_ptr = rhs_val; } - #[cfg(feature = "no_shared")] - { - *lhs_ptr = rhs_val; - } Ok(Default::default()) } // Op-assignment - in order of precedence: @@ -1394,16 +1411,11 @@ impl Engine { ) .map_err(|err| err.new_position(*op_pos))?; - #[cfg(not(feature = "no_shared"))] - if lhs_ptr.is_shared() { + if cfg!(not(feature = "no_shared")) && lhs_ptr.is_shared() { *lhs_ptr.write_lock::().unwrap() = value; } else { *lhs_ptr = value; } - #[cfg(feature = "no_shared")] - { - *lhs_ptr = value; - } } Ok(Default::default()) } @@ -1845,13 +1857,15 @@ impl Engine { .map_err(|err| err.new_position(stmt.position())) } + /// Check a result to ensure that the data size is within allowable limit. + /// Position in `EvalAltResult` may be None and should be set afterwards. #[cfg(feature = "unchecked")] #[inline(always)] fn check_data_size( &self, result: Result>, ) -> Result> { - return result; + result } /// Check a result to ensure that the data size is within allowable limit. @@ -1861,9 +1875,6 @@ impl Engine { &self, result: Result>, ) -> Result> { - #[cfg(feature = "unchecked")] - return result; - // If no data size limits, just return if self.limits.max_string_size + self.limits.max_array_size + self.limits.max_map_size == 0 { diff --git a/src/fn_call.rs b/src/fn_call.rs index d680800a..cbddcff3 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -20,9 +20,8 @@ use crate::utils::StaticVec; #[cfg(not(feature = "no_function"))] use crate::{ - parser::ScriptFnDef, - r#unsafe::unsafe_cast_var_name_to_lifetime, - scope::{Entry as ScopeEntry, EntryType as ScopeEntryType}, + parser::ScriptFnDef, r#unsafe::unsafe_cast_var_name_to_lifetime, + scope::EntryType as ScopeEntryType, }; #[cfg(not(feature = "no_float"))] @@ -34,18 +33,23 @@ use crate::engine::{FN_IDX_GET, FN_IDX_SET}; #[cfg(not(feature = "no_object"))] use crate::engine::{Map, Target, FN_GET, FN_SET}; +#[cfg(not(feature = "no_capture"))] +use crate::scope::Entry as ScopeEntry; + use crate::stdlib::{ any::{type_name, TypeId}, boxed::Box, - collections::HashSet, convert::TryFrom, format, iter::{empty, once}, mem, - string::{String, ToString}, + string::ToString, vec::Vec, }; +#[cfg(not(feature = "no_capture"))] +use crate::stdlib::{collections::HashSet, string::String}; + /// Extract the property name from a getter function name. #[inline(always)] fn extract_prop_from_getter(_fn_name: &str) -> Option<&str> { @@ -422,7 +426,7 @@ impl Engine { is_ref: bool, is_method: bool, pub_only: bool, - capture: Option, + _capture: Option, def_val: Option, level: usize, ) -> Result<(Dynamic, bool), Box> { @@ -451,12 +455,6 @@ impl Engine { ))) } - // Fn - KEYWORD_IS_SHARED if args.len() == 1 => Err(Box::new(EvalAltResult::ErrorRuntime( - "'is_shared' should not be called in method style. Try is_shared(...);".into(), - Position::none(), - ))), - // eval - reaching this point it must be a method-style call KEYWORD_EVAL if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) => @@ -478,7 +476,7 @@ impl Engine { // Add captured variables into scope #[cfg(not(feature = "no_capture"))] - if let Some(captured) = capture { + if let Some(captured) = _capture { add_captured_variables_into_scope(&func.externals, captured, scope); } @@ -662,22 +660,18 @@ impl Engine { .into(), false, )) - } else if _fn_name == KEYWORD_SHARED && idx.is_empty() { + } else if cfg!(not(feature = "no_shared")) + && _fn_name == KEYWORD_IS_SHARED + && idx.is_empty() + { // take call - #[cfg(not(feature = "no_shared"))] - { - Ok((obj.clone().into_shared(), false)) - } - #[cfg(feature = "no_shared")] - unreachable!() - } else if _fn_name == KEYWORD_TAKE && idx.is_empty() { + Ok((target.is_shared().into(), false)) + } else if cfg!(not(feature = "no_shared")) && _fn_name == KEYWORD_SHARED && idx.is_empty() { // take call - #[cfg(not(feature = "no_shared"))] - { - Ok((obj.clone_inner_data::().unwrap(), false)) - } - #[cfg(feature = "no_shared")] - unreachable!() + Ok((obj.clone().into_shared(), false)) + } else if cfg!(not(feature = "no_shared")) && _fn_name == KEYWORD_TAKE && idx.is_empty() { + // take call + Ok((obj.clone_inner_data::().unwrap(), false)) } else { #[cfg(not(feature = "no_object"))] let redirected; @@ -790,8 +784,7 @@ impl Engine { } // Handle is_shared() - #[cfg(not(feature = "no_shared"))] - if name == KEYWORD_IS_SHARED && args_expr.len() == 1 { + if cfg!(not(feature = "no_shared")) && name == KEYWORD_IS_SHARED && args_expr.len() == 1 { let expr = args_expr.get(0).unwrap(); let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; @@ -799,8 +792,7 @@ impl Engine { } // Handle shared() - #[cfg(not(feature = "no_shared"))] - if name == KEYWORD_SHARED && args_expr.len() == 1 { + if cfg!(not(feature = "no_shared")) && name == KEYWORD_SHARED && args_expr.len() == 1 { let expr = args_expr.get(0).unwrap(); let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; @@ -808,8 +800,7 @@ impl Engine { } // Handle take() - #[cfg(not(feature = "no_shared"))] - if name == KEYWORD_TAKE && args_expr.len() == 1 { + if cfg!(not(feature = "no_shared")) && name == KEYWORD_TAKE && args_expr.len() == 1 { let expr = args_expr.get(0).unwrap(); let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; @@ -875,7 +866,7 @@ impl Engine { let mut arg_values: StaticVec<_>; let mut args: StaticVec<_>; let mut is_ref = false; - let capture = if capture && !scope.is_empty() { + let capture = if cfg!(not(feature = "no_capture")) && capture && !scope.is_empty() { Some(scope.flatten_clone()) } else { None @@ -943,18 +934,10 @@ impl Engine { args_expr: &[Expr], def_val: Option, hash_script: u64, - capture: bool, + _capture: bool, level: usize, ) -> Result> { let modules = modules.as_ref().unwrap(); - - #[cfg(not(feature = "no_capture"))] - let capture = if capture && !scope.is_empty() { - Some(scope.flatten_clone()) - } else { - None - }; - let mut arg_values: StaticVec<_>; let mut args: StaticVec<_>; @@ -1027,8 +1010,12 @@ impl Engine { // Add captured variables into scope #[cfg(not(feature = "no_capture"))] - if let Some(captured) = capture { - add_captured_variables_into_scope(&func.externals, captured, scope); + if _capture && !scope.is_empty() { + add_captured_variables_into_scope( + &func.externals, + scope.flatten_clone(), + scope, + ); } self.call_script_fn(scope, mods, state, lib, &mut None, name, func, args, level) @@ -1073,30 +1060,30 @@ pub fn run_builtin_binary_op( let x = x.clone().cast::(); let y = y.clone().cast::(); - #[cfg(not(feature = "unchecked"))] - match op { - "+" => return add(x, y).map(Into::into).map(Some), - "-" => return sub(x, y).map(Into::into).map(Some), - "*" => return mul(x, y).map(Into::into).map(Some), - "/" => return div(x, y).map(Into::into).map(Some), - "%" => return modulo(x, y).map(Into::into).map(Some), - "~" => return pow_i_i(x, y).map(Into::into).map(Some), - ">>" => return shr(x, y).map(Into::into).map(Some), - "<<" => return shl(x, y).map(Into::into).map(Some), - _ => (), - } - - #[cfg(feature = "unchecked")] - match op { - "+" => return Ok(Some((x + y).into())), - "-" => return Ok(Some((x - y).into())), - "*" => return Ok(Some((x * y).into())), - "/" => return Ok(Some((x / y).into())), - "%" => return Ok(Some((x % y).into())), - "~" => return pow_i_i_u(x, y).map(Into::into).map(Some), - ">>" => return shr_u(x, y).map(Into::into).map(Some), - "<<" => return shl_u(x, y).map(Into::into).map(Some), - _ => (), + if cfg!(not(feature = "unchecked")) { + match op { + "+" => return add(x, y).map(Into::into).map(Some), + "-" => return sub(x, y).map(Into::into).map(Some), + "*" => return mul(x, y).map(Into::into).map(Some), + "/" => return div(x, y).map(Into::into).map(Some), + "%" => return modulo(x, y).map(Into::into).map(Some), + "~" => return pow_i_i(x, y).map(Into::into).map(Some), + ">>" => return shr(x, y).map(Into::into).map(Some), + "<<" => return shl(x, y).map(Into::into).map(Some), + _ => (), + } + } else { + match op { + "+" => return Ok(Some((x + y).into())), + "-" => return Ok(Some((x - y).into())), + "*" => return Ok(Some((x * y).into())), + "/" => return Ok(Some((x / y).into())), + "%" => return Ok(Some((x % y).into())), + "~" => return pow_i_i_u(x, y).map(Into::into).map(Some), + ">>" => return shr_u(x, y).map(Into::into).map(Some), + "<<" => return shl_u(x, y).map(Into::into).map(Some), + _ => (), + } } match op { diff --git a/src/optimize.rs b/src/optimize.rs index 3244540b..9072a550 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -576,17 +576,13 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // First search in functions lib (can override built-in) // Cater for both normal function call style and method call style (one additional arguments) - #[cfg(not(feature = "no_function"))] - let _has_script_fn = state.lib.iter_fn().find(|(_, _, _, f)| { + let has_script_fn = cfg!(not(feature = "no_function")) && state.lib.iter_fn().find(|(_, _, _, f)| { if !f.is_script() { return false; } let fn_def = f.get_fn_def(); fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len()) }).is_some(); - #[cfg(feature = "no_function")] - let _has_script_fn: bool = false; - - if _has_script_fn { + if has_script_fn { // A script-defined function overrides the built-in function - do not make the call x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect(); return Expr::FnCall(x); @@ -748,11 +744,13 @@ pub fn optimize_into_ast( _functions: Vec, level: OptimizationLevel, ) -> AST { - #[cfg(feature = "no_optimize")] - const level: OptimizationLevel = OptimizationLevel::None; + let level = if cfg!(feature = "no_optimize") { + OptimizationLevel::None + } else { + level + }; - #[cfg(not(feature = "no_function"))] - let lib = { + let lib = if cfg!(not(feature = "no_function")) { let mut module = Module::new(); if !level.is_none() { @@ -814,11 +812,10 @@ pub fn optimize_into_ast( } module + } else { + Default::default() }; - #[cfg(feature = "no_function")] - let lib = Default::default(); - AST::new( match level { OptimizationLevel::None => statements, diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index fd95e675..d8e256cc 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -2,38 +2,29 @@ use crate::def_package; use crate::module::FuncReturn; use crate::parser::INT; -#[cfg(not(feature = "unchecked"))] use crate::{result::EvalAltResult, token::Position}; #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; -#[cfg(not(feature = "no_float"))] -#[cfg(feature = "no_std")] -use num_traits::*; - -#[cfg(not(feature = "unchecked"))] use num_traits::{ identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr, CheckedSub, }; -#[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "only_i64"))] -use crate::stdlib::ops::{BitAnd, BitOr, BitXor}; +#[cfg(feature = "no_std")] +#[cfg(not(feature = "no_float"))] +use num_traits::float::Float; -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] -use crate::stdlib::ops::{Add, Div, Mul, Neg, Rem, Sub}; - -#[cfg(feature = "unchecked")] -use crate::stdlib::ops::{Shl, Shr}; - -#[cfg(not(feature = "unchecked"))] -use crate::stdlib::{boxed::Box, fmt::Display, format}; +use crate::stdlib::{ + boxed::Box, + fmt::Display, + format, + ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub}, +}; // Checked add -#[cfg(not(feature = "unchecked"))] -pub(crate) fn add(x: T, y: T) -> FuncReturn { +pub fn add(x: T, y: T) -> FuncReturn { x.checked_add(&y).ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( format!("Addition overflow: {} + {}", x, y), @@ -42,8 +33,7 @@ pub(crate) fn add(x: T, y: T) -> FuncReturn { }) } // Checked subtract -#[cfg(not(feature = "unchecked"))] -pub(crate) fn sub(x: T, y: T) -> FuncReturn { +pub fn sub(x: T, y: T) -> FuncReturn { x.checked_sub(&y).ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( format!("Subtraction underflow: {} - {}", x, y), @@ -52,8 +42,7 @@ pub(crate) fn sub(x: T, y: T) -> FuncReturn { }) } // Checked multiply -#[cfg(not(feature = "unchecked"))] -pub(crate) fn mul(x: T, y: T) -> FuncReturn { +pub fn mul(x: T, y: T) -> FuncReturn { x.checked_mul(&y).ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( format!("Multiplication overflow: {} * {}", x, y), @@ -62,8 +51,7 @@ pub(crate) fn mul(x: T, y: T) -> FuncReturn { }) } // Checked divide -#[cfg(not(feature = "unchecked"))] -pub(crate) fn div(x: T, y: T) -> FuncReturn +pub fn div(x: T, y: T) -> FuncReturn where T: Display + CheckedDiv + PartialEq + Zero, { @@ -83,8 +71,7 @@ where }) } // Checked negative - e.g. -(i32::MIN) will overflow i32::MAX -#[cfg(not(feature = "unchecked"))] -pub(crate) fn neg(x: T) -> FuncReturn { +pub fn neg(x: T) -> FuncReturn { x.checked_neg().ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( format!("Negation overflow: -{}", x), @@ -93,8 +80,7 @@ pub(crate) fn neg(x: T) -> FuncReturn { }) } // Checked absolute -#[cfg(not(feature = "unchecked"))] -pub(crate) fn abs(x: T) -> FuncReturn { +pub fn abs(x: T) -> FuncReturn { // FIX - We don't use Signed::abs() here because, contrary to documentation, it panics // when the number is ::MIN instead of returning ::MIN itself. if x >= ::zero() { @@ -109,32 +95,26 @@ pub(crate) fn abs(x: T) -> FuncRetu } } // Unchecked add - may panic on overflow -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn add_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x + y) } // Unchecked subtract - may panic on underflow -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn sub_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x - y) } // Unchecked multiply - may panic on overflow -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn mul_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x * y) } // Unchecked divide - may panic when dividing by zero -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn div_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x / y) } // Unchecked negative - may panic on overflow -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn neg_u(x: T) -> FuncReturn<::Output> { Ok(-x) } // Unchecked absolute - may panic on overflow -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn abs_u(x: T) -> FuncReturn<::Output> where T: Neg + PartialOrd + Default + Into<::Output>, @@ -147,24 +127,17 @@ where } } // Bit operators -#[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "only_i64"))] fn binary_and(x: T, y: T) -> FuncReturn<::Output> { Ok(x & y) } -#[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "only_i64"))] fn binary_or(x: T, y: T) -> FuncReturn<::Output> { Ok(x | y) } -#[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "only_i64"))] fn binary_xor(x: T, y: T) -> FuncReturn<::Output> { Ok(x ^ y) } // Checked left-shift -#[cfg(not(feature = "unchecked"))] -pub(crate) fn shl(x: T, y: INT) -> FuncReturn { +pub fn shl(x: T, y: INT) -> FuncReturn { // Cannot shift by a negative number of bits if y < 0 { return Err(Box::new(EvalAltResult::ErrorArithmetic( @@ -181,8 +154,7 @@ pub(crate) fn shl(x: T, y: INT) -> FuncReturn { }) } // Checked right-shift -#[cfg(not(feature = "unchecked"))] -pub(crate) fn shr(x: T, y: INT) -> FuncReturn { +pub fn shr(x: T, y: INT) -> FuncReturn { // Cannot shift by a negative number of bits if y < 0 { return Err(Box::new(EvalAltResult::ErrorArithmetic( @@ -199,18 +171,15 @@ pub(crate) fn shr(x: T, y: INT) -> FuncReturn { }) } // Unchecked left-shift - may panic if shifting by a negative number of bits -#[cfg(feature = "unchecked")] -pub(crate) fn shl_u>(x: T, y: T) -> FuncReturn<>::Output> { +pub fn shl_u>(x: T, y: T) -> FuncReturn<>::Output> { Ok(x.shl(y)) } // Unchecked right-shift - may panic if shifting by a negative number of bits -#[cfg(feature = "unchecked")] -pub(crate) fn shr_u>(x: T, y: T) -> FuncReturn<>::Output> { +pub fn shr_u>(x: T, y: T) -> FuncReturn<>::Output> { Ok(x.shr(y)) } // Checked modulo -#[cfg(not(feature = "unchecked"))] -pub(crate) fn modulo(x: T, y: T) -> FuncReturn { +pub fn modulo(x: T, y: T) -> FuncReturn { x.checked_rem(&y).ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( format!("Modulo division by zero or overflow: {} % {}", x, y), @@ -219,62 +188,58 @@ pub(crate) fn modulo(x: T, y: T) -> FuncReturn { }) } // Unchecked modulo - may panic if dividing by zero -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn modulo_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x % y) } // Checked power -#[cfg(not(feature = "unchecked"))] -pub(crate) fn pow_i_i(x: INT, y: INT) -> FuncReturn { - #[cfg(not(feature = "only_i32"))] - if y > (u32::MAX as INT) { - Err(Box::new(EvalAltResult::ErrorArithmetic( - format!("Integer raised to too large an index: {} ~ {}", x, y), - Position::none(), - ))) - } else if y < 0 { - Err(Box::new(EvalAltResult::ErrorArithmetic( - format!("Integer raised to a negative index: {} ~ {}", x, y), - Position::none(), - ))) - } else { - x.checked_pow(y as u32).ok_or_else(|| { - Box::new(EvalAltResult::ErrorArithmetic( - format!("Power overflow: {} ~ {}", x, y), +pub fn pow_i_i(x: INT, y: INT) -> FuncReturn { + if cfg!(not(feature = "only_i32")) { + if y > (u32::MAX as INT) { + Err(Box::new(EvalAltResult::ErrorArithmetic( + format!("Integer raised to too large an index: {} ~ {}", x, y), Position::none(), - )) - }) - } - - #[cfg(feature = "only_i32")] - if y < 0 { - Err(Box::new(EvalAltResult::ErrorArithmetic( - format!("Integer raised to a negative index: {} ~ {}", x, y), - Position::none(), - ))) - } else { - x.checked_pow(y as u32).ok_or_else(|| { - Box::new(EvalAltResult::ErrorArithmetic( - format!("Power overflow: {} ~ {}", x, y), + ))) + } else if y < 0 { + Err(Box::new(EvalAltResult::ErrorArithmetic( + format!("Integer raised to a negative index: {} ~ {}", x, y), Position::none(), - )) - }) + ))) + } else { + x.checked_pow(y as u32).ok_or_else(|| { + Box::new(EvalAltResult::ErrorArithmetic( + format!("Power overflow: {} ~ {}", x, y), + Position::none(), + )) + }) + } + } else { + if y < 0 { + Err(Box::new(EvalAltResult::ErrorArithmetic( + format!("Integer raised to a negative index: {} ~ {}", x, y), + Position::none(), + ))) + } else { + x.checked_pow(y as u32).ok_or_else(|| { + Box::new(EvalAltResult::ErrorArithmetic( + format!("Power overflow: {} ~ {}", x, y), + Position::none(), + )) + }) + } } } // Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX) -#[cfg(feature = "unchecked")] -pub(crate) fn pow_i_i_u(x: INT, y: INT) -> FuncReturn { +pub fn pow_i_i_u(x: INT, y: INT) -> FuncReturn { Ok(x.pow(y as u32)) } // Floating-point power - always well-defined #[cfg(not(feature = "no_float"))] -pub(crate) fn pow_f_f(x: FLOAT, y: FLOAT) -> FuncReturn { +pub fn pow_f_f(x: FLOAT, y: FLOAT) -> FuncReturn { Ok(x.powf(y)) } // Checked power #[cfg(not(feature = "no_float"))] -#[cfg(not(feature = "unchecked"))] -pub(crate) fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn { +pub fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn { // Raise to power that is larger than an i32 if y > (i32::MAX as INT) { return Err(Box::new(EvalAltResult::ErrorArithmetic( @@ -286,9 +251,8 @@ pub(crate) fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn { Ok(x.powi(y as i32)) } // Unchecked power - may be incorrect if the power index is too high (> i32::MAX) -#[cfg(feature = "unchecked")] #[cfg(not(feature = "no_float"))] -pub(crate) fn pow_f_i_u(x: FLOAT, y: INT) -> FuncReturn { +pub fn pow_f_i_u(x: FLOAT, y: INT) -> FuncReturn { Ok(x.powi(y as i32)) } @@ -317,11 +281,8 @@ macro_rules! reg_sign { } def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { - #[cfg(not(feature = "unchecked"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { + if cfg!(not(feature = "unchecked")) { // Checked basic arithmetic reg_op!(lib, "+", add, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, u32, u64); @@ -332,8 +293,7 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { reg_op!(lib, ">>", shr, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "%", modulo, i8, u8, i16, u16, i32, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "+", add, i128, u128); reg_op!(lib, "-", sub, i128, u128); reg_op!(lib, "*", mul, i128, u128); @@ -345,8 +305,7 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { } } - #[cfg(feature = "unchecked")] - { + if cfg!(feature = "unchecked") { // Unchecked basic arithmetic reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, u32, u64); @@ -357,8 +316,7 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { reg_op!(lib, ">>", shr_u, i64, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "%", modulo_u, i8, u8, i16, u16, i32, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "+", add_u, i128, u128); reg_op!(lib, "-", sub_u, i128, u128); reg_op!(lib, "*", mul_u, i128, u128); @@ -372,13 +330,13 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { reg_sign!(lib, "sign", INT, i8, i16, i32, i64); - #[cfg(not(target_arch = "wasm32"))] - reg_sign!(lib, "sign", INT, i128); + if cfg!(not(target_arch = "wasm32")) { + reg_sign!(lib, "sign", INT, i128); + } } // Basic arithmetic for floating-point - no need to check - #[cfg(not(feature = "no_float"))] - { + if cfg!(not(feature = "no_float")) { reg_op!(lib, "+", add_u, f32); reg_op!(lib, "-", sub_u, f32); reg_op!(lib, "*", mul_u, f32); @@ -387,15 +345,12 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { reg_sign!(lib, "sign", f64, f64); } - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_op!(lib, "|", binary_or, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "&", binary_and, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "^", binary_xor, i8, u8, i16, u16, i32, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "|", binary_or, i128, u128); reg_op!(lib, "&", binary_and, i128, u128); reg_op!(lib, "^", binary_xor, i128, u128); @@ -405,12 +360,11 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { #[cfg(not(feature = "no_float"))] { // Checked power - #[cfg(not(feature = "unchecked"))] - lib.set_fn_2("~", pow_f_i); - - // Unchecked power - #[cfg(feature = "unchecked")] - lib.set_fn_2("~", pow_f_i_u); + if cfg!(not(feature = "unchecked")) { + lib.set_fn_2("~", pow_f_i); + } else { + lib.set_fn_2("~", pow_f_i_u); + } // Floating-point modulo and power reg_op!(lib, "%", modulo_u, f32); @@ -421,19 +375,15 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { } // Checked unary - #[cfg(not(feature = "unchecked"))] - { + if cfg!(not(feature = "unchecked")) { reg_unary!(lib, "-", neg, INT); reg_unary!(lib, "abs", abs, INT); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_unary!(lib, "-", neg, i8, i16, i32, i64); reg_unary!(lib, "abs", abs, i8, i16, i32, i64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_unary!(lib, "-", neg, i128); reg_unary!(lib, "abs", abs, i128); } @@ -441,19 +391,15 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { } // Unchecked unary - #[cfg(feature = "unchecked")] - { + if cfg!(feature = "unchecked") { reg_unary!(lib, "-", neg_u, INT); reg_unary!(lib, "abs", abs_u, INT); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_unary!(lib, "-", neg_u, i8, i16, i32, i64); reg_unary!(lib, "abs", abs_u, i8, i16, i32, i64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_unary!(lib, "-", neg_u, i128); reg_unary!(lib, "abs", abs_u, i128); } diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 5ca3b1c7..c246f1d5 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -82,7 +82,6 @@ macro_rules! reg_pad { }; } -#[cfg(not(feature = "no_index"))] def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { reg_op!(lib, "push", push, INT, bool, char, ImmutableString, Array, ()); reg_pad!(lib, "pad", pad, INT, bool, char, ImmutableString, Array, ()); @@ -104,15 +103,12 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { }, ); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_op!(lib, "push", push, i8, u8, i16, u16, i32, i64, u32, u64); reg_pad!(lib, "pad", pad, i8, u8, i16, u16, i32, u32, i64, u64); reg_tri!(lib, "insert", ins, i8, u8, i16, u16, i32, i64, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "push", push, i128, u128); reg_pad!(lib, "pad", pad, i128, u128); reg_tri!(lib, "insert", ins, i128, u128); diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index cccf4b6c..8914c6a5 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -73,9 +73,7 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { reg_range::(lib); lib.set_fn_2("range", get_range::); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { macro_rules! reg_range { ($lib:expr, $x:expr, $( $y:ty ),*) => ( $( @@ -87,16 +85,15 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { reg_range!(lib, "range", i8, u8, i16, u16, i32, i64, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - reg_range!(lib, "range", i128, u128); + if cfg!(not(target_arch = "wasm32")) { + reg_range!(lib, "range", i128, u128); + } } reg_step::(lib); lib.set_fn_3("range", get_step_range::); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { macro_rules! reg_step { ($lib:expr, $x:expr, $( $y:ty ),*) => ( $( @@ -108,7 +105,8 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { reg_step!(lib, "range", i8, u8, i16, u16, i32, i64, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - reg_step!(lib, "range", i128, u128); + if cfg!(not(target_arch = "wasm32")) { + reg_step!(lib, "range", i128, u128); + } } }); diff --git a/src/packages/logic.rs b/src/packages/logic.rs index 1cb4b437..6c70a38c 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -33,9 +33,7 @@ macro_rules! reg_op { } def_package!(crate:LogicPackage:"Logical operators.", lib, { - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_op!(lib, "<", lt, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "<=", lte, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, ">", gt, i8, u8, i16, u16, i32, u32, u64); @@ -43,8 +41,7 @@ def_package!(crate:LogicPackage:"Logical operators.", lib, { reg_op!(lib, "==", eq, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "!=", ne, i8, u8, i16, u16, i32, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "<", lt, i128, u128); reg_op!(lib, "<=", lte, i128, u128); reg_op!(lib, ">", gt, i128, u128); diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 9437bc67..4e2e58ad 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -1,25 +1,20 @@ #![cfg(not(feature = "no_object"))] +use crate::any::Dynamic; use crate::def_package; use crate::engine::Map; +use crate::module::FuncReturn; use crate::parser::{ImmutableString, INT}; -#[cfg(not(feature = "no_index"))] -use crate::{any::Dynamic, module::FuncReturn}; - -#[cfg(not(feature = "no_index"))] use crate::stdlib::vec::Vec; -#[cfg(not(feature = "no_index"))] fn map_get_keys(map: &mut Map) -> FuncReturn> { Ok(map.iter().map(|(k, _)| k.clone().into()).collect()) } -#[cfg(not(feature = "no_index"))] fn map_get_values(map: &mut Map) -> FuncReturn> { Ok(map.iter().map(|(_, v)| v.clone()).collect()) } -#[cfg(not(feature = "no_object"))] def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { lib.set_fn_2_mut( "has", @@ -74,9 +69,11 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { ); // Register map access functions - #[cfg(not(feature = "no_index"))] - lib.set_fn_1_mut("keys", map_get_keys); + if cfg!(not(feature = "no_index")) { + lib.set_fn_1_mut("keys", map_get_keys); + } - #[cfg(not(feature = "no_index"))] - lib.set_fn_1_mut("values", map_get_values); + if cfg!(not(feature = "no_index")) { + lib.set_fn_1_mut("values", map_get_values); + } }); diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index 55bb68a3..f8fd2d33 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -5,22 +5,20 @@ use crate::parser::INT; use crate::parser::FLOAT; #[cfg(not(feature = "no_float"))] -#[cfg(not(feature = "unchecked"))] use crate::{result::EvalAltResult, token::Position}; -#[cfg(not(feature = "no_float"))] #[cfg(feature = "no_std")] -use num_traits::*; +#[cfg(not(feature = "no_float"))] +use num_traits::float::Float; #[cfg(not(feature = "no_float"))] -#[cfg(not(feature = "unchecked"))] use crate::stdlib::{boxed::Box, format}; +#[allow(dead_code)] #[cfg(feature = "only_i32")] -#[cfg(not(feature = "unchecked"))] pub const MAX_INT: INT = i32::MAX; +#[allow(dead_code)] #[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "unchecked"))] pub const MAX_INT: INT = i64::MAX; def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { @@ -69,9 +67,7 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { lib.set_fn_1("to_float", |x: INT| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: f32| Ok(x as FLOAT)); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { lib.set_fn_1("to_float", |x: i8| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: u8| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: i16| Ok(x as FLOAT)); @@ -81,8 +77,7 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { lib.set_fn_1("to_float", |x: i64| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: u64| Ok(x as FLOAT)); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { lib.set_fn_1("to_float", |x: i128| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: u128| Ok(x as FLOAT)); } @@ -91,28 +86,25 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { lib.set_fn_1("to_int", |ch: char| Ok(ch as INT)); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { lib.set_fn_1("to_int", |x: i8| Ok(x as INT)); lib.set_fn_1("to_int", |x: u8| Ok(x as INT)); lib.set_fn_1("to_int", |x: i16| Ok(x as INT)); lib.set_fn_1("to_int", |x: u16| Ok(x as INT)); } - #[cfg(not(feature = "only_i32"))] - { + if cfg!(not(feature = "only_i32")) { lib.set_fn_1("to_int", |x: i32| Ok(x as INT)); lib.set_fn_1("to_int", |x: u64| Ok(x as INT)); - #[cfg(feature = "only_i64")] - lib.set_fn_1("to_int", |x: u32| Ok(x as INT)); + if cfg!(feature = "only_i64") { + lib.set_fn_1("to_int", |x: u32| Ok(x as INT)); + } } #[cfg(not(feature = "no_float"))] { - #[cfg(not(feature = "unchecked"))] - { + if cfg!(not(feature = "unchecked")) { lib.set_fn_1( "to_int", |x: f32| { @@ -141,8 +133,7 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { ); } - #[cfg(feature = "unchecked")] - { + if cfg!(feature = "unchecked") { lib.set_fn_1("to_int", |x: f32| Ok(x as INT)); lib.set_fn_1("to_int", |x: f64| Ok(x as INT)); } diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 2e405004..36f4f333 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -48,9 +48,7 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin reg_op!(lib, KEYWORD_DEBUG, to_debug, INT, bool, (), char, ImmutableString); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_op!(lib, KEYWORD_PRINT, to_string, i8, u8, i16, u16, i32, u32); reg_op!(lib, FN_TO_STRING, to_string, i8, u8, i16, u16, i32, u32); reg_op!(lib, KEYWORD_DEBUG, to_debug, i8, u8, i16, u16, i32, u32); @@ -58,8 +56,7 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin reg_op!(lib, FN_TO_STRING, to_string, i64, u64); reg_op!(lib, KEYWORD_DEBUG, to_debug, i64, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, KEYWORD_PRINT, to_string, i128, u128); reg_op!(lib, FN_TO_STRING, to_string, i128, u128); reg_op!(lib, KEYWORD_DEBUG, to_debug, i128, u128); diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index b50d6915..130af8b8 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -94,14 +94,11 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str reg_op!(lib, "+", prepend, INT, bool, char); lib.set_fn_2("+", |_: (), y: ImmutableString| Ok(y)); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_op!(lib, "+", append, i8, u8, i16, u16, i32, i64, u32, u64); reg_op!(lib, "+", prepend, i8, u8, i16, u16, i32, i64, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "+", append, i128, u128); reg_op!(lib, "+", prepend, i128, u128); } diff --git a/src/parser.rs b/src/parser.rs index 2f832a75..08869146 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,7 +2,9 @@ use crate::any::{Dynamic, Union}; use crate::calc_fn_hash; -use crate::engine::{Engine, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; +use crate::engine::{ + Engine, KEYWORD_FN_PTR_CURRY, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT, +}; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::fn_native::Shared; use crate::module::{Module, ModuleRef}; @@ -15,9 +17,6 @@ use crate::utils::{StaticVec, StraightHasherBuilder}; #[cfg(not(feature = "no_function"))] use crate::engine::FN_ANONYMOUS; -#[cfg(not(feature = "no_capture"))] -use crate::engine::KEYWORD_FN_PTR_CURRY; - #[cfg(not(feature = "no_object"))] use crate::engine::{make_getter, make_setter}; @@ -25,7 +24,7 @@ use crate::stdlib::{ borrow::Cow, boxed::Box, char, - collections::{HashMap, HashSet}, + collections::HashMap, fmt, format, hash::{Hash, Hasher}, iter::empty, @@ -40,6 +39,9 @@ use crate::stdlib::{ #[cfg(not(feature = "no_function"))] use crate::stdlib::collections::hash_map::DefaultHasher; +#[cfg(not(feature = "no_capture"))] +use crate::stdlib::collections::HashSet; + #[cfg(feature = "no_std")] #[cfg(not(feature = "no_function"))] use ahash::AHasher; @@ -2211,11 +2213,13 @@ fn parse_binary_op( let (op_token, pos) = input.next().unwrap(); - #[cfg(any(not(feature = "no_object"), not(feature = "no_capture")))] - if op_token == Token::Period { + if cfg!(not(feature = "no_object")) && op_token == Token::Period { if let (Token::Identifier(_), _) = input.peek().unwrap() { // prevents capturing of the object properties as vars: xxx. - state.capture = false; + #[cfg(not(feature = "no_capture"))] + { + state.capture = false; + } } } @@ -3112,7 +3116,6 @@ fn parse_fn( } /// Creates a curried expression from a list of external variables -#[cfg(not(feature = "no_capture"))] fn make_curry_from_externals( fn_expr: Expr, externals: StaticVec<(String, Position)>, @@ -3209,26 +3212,31 @@ fn parse_anon_fn( let body = parse_stmt(input, state, lib, settings.level_up()) .map(|stmt| stmt.unwrap_or_else(|| Stmt::Noop(pos)))?; - #[cfg(feature = "no_capture")] - let params: StaticVec<_> = params.into_iter().map(|(v, _)| v).collect(); - // External variables may need to be processed in a consistent order, // so extract them into a list. - #[cfg(not(feature = "no_capture"))] - let externals: StaticVec<_> = state - .externals - .iter() - .map(|(k, &v)| (k.clone(), v)) - .collect(); + let externals: StaticVec<_> = { + #[cfg(not(feature = "no_capture"))] + { + state + .externals + .iter() + .map(|(k, &v)| (k.clone(), v)) + .collect() + } + #[cfg(feature = "no_capture")] + Default::default() + }; - // Add parameters that are auto-curried - #[cfg(not(feature = "no_capture"))] - let params: StaticVec<_> = externals - .iter() - .map(|(k, _)| k) - .cloned() - .chain(params.into_iter().map(|(v, _)| v)) - .collect(); + let params: StaticVec<_> = if cfg!(not(feature = "no_capture")) { + externals + .iter() + .map(|(k, _)| k) + .cloned() + .chain(params.into_iter().map(|(v, _)| v)) + .collect() + } else { + params.into_iter().map(|(v, _)| v).collect() + }; // Calculate hash #[cfg(feature = "no_std")] @@ -3257,8 +3265,11 @@ fn parse_anon_fn( let expr = Expr::FnPointer(Box::new((fn_name, settings.pos))); - #[cfg(not(feature = "no_capture"))] - let expr = make_curry_from_externals(expr, externals, settings.pos); + let expr = if cfg!(not(feature = "no_capture")) { + make_curry_from_externals(expr, externals, settings.pos) + } else { + expr + }; Ok((expr, script)) } diff --git a/src/scope.rs b/src/scope.rs index b1b9a3b3..37f71e99 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -158,6 +158,32 @@ impl<'a> Scope<'a> { self.push_dynamic_value(name, EntryType::Normal, Dynamic::from(value), false) } + /// Add (push) a new shared entry to the Scope. + /// + /// # Examples + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::new(); + /// + /// my_scope.push_shared("x", 42_i64); + /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); + /// ``` + #[cfg(not(feature = "no_shared"))] + pub fn push_shared>, T: Variant + Clone>( + &mut self, + name: K, + value: T, + ) -> &mut Self { + self.push_dynamic_value( + name, + EntryType::Normal, + Dynamic::from(value).into_shared(), + false, + ) + } + /// Add (push) a new `Dynamic` entry to the Scope. /// /// # Examples @@ -200,6 +226,34 @@ impl<'a> Scope<'a> { self.push_dynamic_value(name, EntryType::Constant, Dynamic::from(value), true) } + /// Add (push) a new shared constant to the Scope. + /// + /// Shared constants are immutable and cannot be assigned to, but their shared values can change. + /// + /// # Examples + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::new(); + /// + /// my_scope.push_constant_shared("x", 42_i64); + /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); + /// ``` + #[cfg(not(feature = "no_shared"))] + pub fn push_constant_shared>, T: Variant + Clone>( + &mut self, + name: K, + value: T, + ) -> &mut Self { + self.push_dynamic_value( + name, + EntryType::Constant, + Dynamic::from(value).into_shared(), + true, + ) + } + /// Add (push) a new constant with a `Dynamic` value to the Scope. /// /// Constants are immutable and cannot be assigned to. Their values never change. @@ -393,7 +447,6 @@ impl<'a> Scope<'a> { /// Clone the Scope, keeping only the last instances of each variable name. /// Shadowed variables are omitted in the copy. - #[cfg(not(feature = "no_capture"))] pub(crate) fn flatten_clone(&self) -> Self { let mut entries: HashMap<&str, Entry> = Default::default(); diff --git a/src/token.rs b/src/token.rs index dfd50c27..920fb33a 100644 --- a/src/token.rs +++ b/src/token.rs @@ -507,8 +507,8 @@ impl Token { | "yield" => Reserved(syntax.into()), KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR - | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_SHARED | KEYWORD_TAKE - | KEYWORD_THIS => Reserved(syntax.into()), + | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_SHARED | KEYWORD_SHARED + | KEYWORD_TAKE | KEYWORD_THIS => Reserved(syntax.into()), _ => return None, }) From 5873bccd506756216d28b265ed4759f672f68619 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 31 Jul 2020 23:37:30 +0800 Subject: [PATCH 45/64] Fix no_index and no_object. --- src/engine.rs | 68 ++++++++++++++++++--------------------------------- 1 file changed, 24 insertions(+), 44 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index cdd9c040..a011bf67 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -734,69 +734,49 @@ impl Engine { _ if _new_val.is_some() => { let mut idx_val2 = idx_val.clone(); - // `next` is introduced to bypass double mutable borrowing of target - #[cfg(not(feature = "no_index"))] - let mut next: Option<(u8, Dynamic)>; - - match self.get_indexed_mut(state, lib, target, idx_val, pos, true, level) { - // Indexed value is an owned value - the only possibility is an indexer - // Try to call an index setter + // `call_setter` is introduced to bypass double mutable borrowing of target + let mut call_setter = match self + .get_indexed_mut(state, lib, target, idx_val, pos, true, level) + { + // Indexed value is an owned value - the only possibility is a value from an indexer. + // Try to call an index setter to update it back, but no need to raise an error + // if the indexer is read-only. #[cfg(not(feature = "no_index"))] - Ok(obj_ptr) if obj_ptr.is_value() => { - next = Some((1, _new_val.unwrap())); - } + Ok(obj_ptr) if obj_ptr.is_value() => Some((false, _new_val.unwrap())), // Indexed value is a reference - update directly Ok(ref mut obj_ptr) => { obj_ptr .set_value(_new_val.unwrap()) .map_err(|err| err.new_position(rhs.position()))?; - #[cfg(not(feature = "no_index"))] - { - next = None; - } + None } Err(err) => match *err { // No index getter - try to call an index setter - #[cfg(not(feature = "no_index"))] EvalAltResult::ErrorIndexingType(_, _) => { - next = Some((2, _new_val.unwrap())); + // Raise error if there is no index getter nor setter + Some((true, _new_val.unwrap())) } - // Error + // Any other error - return err => return Err(Box::new(err)), }, }; - #[cfg(not(feature = "no_index"))] - match &mut next { - // next step is custom index setter call - Some((1, _new_val)) => { - let args = &mut [target.as_mut(), &mut idx_val2, _new_val]; + if let Some((must_have_setter, ref mut new_val)) = call_setter { + let args = &mut [target.as_mut(), &mut idx_val2, new_val]; - self.exec_fn_call( - state, lib, FN_IDX_SET, 0, args, is_ref, true, false, None, - None, level, - ) - .or_else(|err| match *err { + match self.exec_fn_call( + state, lib, FN_IDX_SET, 0, args, is_ref, true, false, None, None, + level, + ) { + Ok(_) => (), + Err(err) if !must_have_setter => match *err { // If there is no index setter, no need to set it back because the indexer is read-only - EvalAltResult::ErrorFunctionNotFound(_, _) => { - Ok(Default::default()) - } - _ => Err(err), - })?; + EvalAltResult::ErrorFunctionNotFound(_, _) => (), + _ => return Err(err), + }, + err => return err, } - - // next step is custom index setter call in case of error - Some((2, _new_val)) => { - let args = &mut [target.as_mut(), &mut idx_val2, _new_val]; - - self.exec_fn_call( - state, lib, FN_IDX_SET, 0, args, is_ref, true, false, None, - None, level, - )?; - } - None => (), - _ => unreachable!(), } Ok(Default::default()) From a2858e0fd31894a5a07f40288aaa6cc55cd3f1ac Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 1 Aug 2020 12:19:27 +0800 Subject: [PATCH 46/64] Refine feature gates --- src/fn_call.rs | 96 ++++++++-------- src/packages/arithmetic.rs | 210 +++++++++++++---------------------- src/packages/array_basic.rs | 8 +- src/packages/iter_basic.rs | 18 ++- src/packages/logic.rs | 7 +- src/packages/map_basic.rs | 19 ++-- src/packages/math_basic.rs | 35 +++--- src/packages/string_basic.rs | 7 +- src/packages/string_more.rs | 9 +- 9 files changed, 164 insertions(+), 245 deletions(-) diff --git a/src/fn_call.rs b/src/fn_call.rs index da029c78..4b40df64 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -1023,30 +1023,30 @@ pub fn run_builtin_binary_op( let x = x.clone().cast::(); let y = y.clone().cast::(); - #[cfg(not(feature = "unchecked"))] - match op { - "+" => return add(x, y).map(Into::into).map(Some), - "-" => return sub(x, y).map(Into::into).map(Some), - "*" => return mul(x, y).map(Into::into).map(Some), - "/" => return div(x, y).map(Into::into).map(Some), - "%" => return modulo(x, y).map(Into::into).map(Some), - "~" => return pow_i_i(x, y).map(Into::into).map(Some), - ">>" => return shr(x, y).map(Into::into).map(Some), - "<<" => return shl(x, y).map(Into::into).map(Some), - _ => (), - } - - #[cfg(feature = "unchecked")] - match op { - "+" => return Ok(Some((x + y).into())), - "-" => return Ok(Some((x - y).into())), - "*" => return Ok(Some((x * y).into())), - "/" => return Ok(Some((x / y).into())), - "%" => return Ok(Some((x % y).into())), - "~" => return pow_i_i_u(x, y).map(Into::into).map(Some), - ">>" => return shr_u(x, y).map(Into::into).map(Some), - "<<" => return shl_u(x, y).map(Into::into).map(Some), - _ => (), + if cfg!(not(feature = "unchecked")) { + match op { + "+" => return add(x, y).map(Into::into).map(Some), + "-" => return sub(x, y).map(Into::into).map(Some), + "*" => return mul(x, y).map(Into::into).map(Some), + "/" => return div(x, y).map(Into::into).map(Some), + "%" => return modulo(x, y).map(Into::into).map(Some), + "~" => return pow_i_i(x, y).map(Into::into).map(Some), + ">>" => return shr(x, y).map(Into::into).map(Some), + "<<" => return shl(x, y).map(Into::into).map(Some), + _ => (), + } + } else { + match op { + "+" => return Ok(Some((x + y).into())), + "-" => return Ok(Some((x - y).into())), + "*" => return Ok(Some((x * y).into())), + "/" => return Ok(Some((x / y).into())), + "%" => return Ok(Some((x % y).into())), + "~" => return pow_i_i_u(x, y).map(Into::into).map(Some), + ">>" => return shr_u(x, y).map(Into::into).map(Some), + "<<" => return shl_u(x, y).map(Into::into).map(Some), + _ => (), + } } match op { @@ -1151,30 +1151,30 @@ pub fn run_builtin_op_assignment( let x = x.downcast_mut::().unwrap(); let y = y.clone().cast::(); - #[cfg(not(feature = "unchecked"))] - match op { - "+=" => return Ok(Some(*x = add(*x, y)?)), - "-=" => return Ok(Some(*x = sub(*x, y)?)), - "*=" => return Ok(Some(*x = mul(*x, y)?)), - "/=" => return Ok(Some(*x = div(*x, y)?)), - "%=" => return Ok(Some(*x = modulo(*x, y)?)), - "~=" => return Ok(Some(*x = pow_i_i(*x, y)?)), - ">>=" => return Ok(Some(*x = shr(*x, y)?)), - "<<=" => return Ok(Some(*x = shl(*x, y)?)), - _ => (), - } - - #[cfg(feature = "unchecked")] - match op { - "+=" => return Ok(Some(*x += y)), - "-=" => return Ok(Some(*x -= y)), - "*=" => return Ok(Some(*x *= y)), - "/=" => return Ok(Some(*x /= y)), - "%=" => return Ok(Some(*x %= y)), - "~=" => return Ok(Some(*x = pow_i_i_u(*x, y)?)), - ">>=" => return Ok(Some(*x = shr_u(*x, y)?)), - "<<=" => return Ok(Some(*x = shl_u(*x, y)?)), - _ => (), + if cfg!(not(feature = "unchecked")) { + match op { + "+=" => return Ok(Some(*x = add(*x, y)?)), + "-=" => return Ok(Some(*x = sub(*x, y)?)), + "*=" => return Ok(Some(*x = mul(*x, y)?)), + "/=" => return Ok(Some(*x = div(*x, y)?)), + "%=" => return Ok(Some(*x = modulo(*x, y)?)), + "~=" => return Ok(Some(*x = pow_i_i(*x, y)?)), + ">>=" => return Ok(Some(*x = shr(*x, y)?)), + "<<=" => return Ok(Some(*x = shl(*x, y)?)), + _ => (), + } + } else { + match op { + "+=" => return Ok(Some(*x += y)), + "-=" => return Ok(Some(*x -= y)), + "*=" => return Ok(Some(*x *= y)), + "/=" => return Ok(Some(*x /= y)), + "%=" => return Ok(Some(*x %= y)), + "~=" => return Ok(Some(*x = pow_i_i_u(*x, y)?)), + ">>=" => return Ok(Some(*x = shr_u(*x, y)?)), + "<<=" => return Ok(Some(*x = shl_u(*x, y)?)), + _ => (), + } } match op { diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index fd95e675..d8e256cc 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -2,38 +2,29 @@ use crate::def_package; use crate::module::FuncReturn; use crate::parser::INT; -#[cfg(not(feature = "unchecked"))] use crate::{result::EvalAltResult, token::Position}; #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; -#[cfg(not(feature = "no_float"))] -#[cfg(feature = "no_std")] -use num_traits::*; - -#[cfg(not(feature = "unchecked"))] use num_traits::{ identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr, CheckedSub, }; -#[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "only_i64"))] -use crate::stdlib::ops::{BitAnd, BitOr, BitXor}; +#[cfg(feature = "no_std")] +#[cfg(not(feature = "no_float"))] +use num_traits::float::Float; -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] -use crate::stdlib::ops::{Add, Div, Mul, Neg, Rem, Sub}; - -#[cfg(feature = "unchecked")] -use crate::stdlib::ops::{Shl, Shr}; - -#[cfg(not(feature = "unchecked"))] -use crate::stdlib::{boxed::Box, fmt::Display, format}; +use crate::stdlib::{ + boxed::Box, + fmt::Display, + format, + ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub}, +}; // Checked add -#[cfg(not(feature = "unchecked"))] -pub(crate) fn add(x: T, y: T) -> FuncReturn { +pub fn add(x: T, y: T) -> FuncReturn { x.checked_add(&y).ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( format!("Addition overflow: {} + {}", x, y), @@ -42,8 +33,7 @@ pub(crate) fn add(x: T, y: T) -> FuncReturn { }) } // Checked subtract -#[cfg(not(feature = "unchecked"))] -pub(crate) fn sub(x: T, y: T) -> FuncReturn { +pub fn sub(x: T, y: T) -> FuncReturn { x.checked_sub(&y).ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( format!("Subtraction underflow: {} - {}", x, y), @@ -52,8 +42,7 @@ pub(crate) fn sub(x: T, y: T) -> FuncReturn { }) } // Checked multiply -#[cfg(not(feature = "unchecked"))] -pub(crate) fn mul(x: T, y: T) -> FuncReturn { +pub fn mul(x: T, y: T) -> FuncReturn { x.checked_mul(&y).ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( format!("Multiplication overflow: {} * {}", x, y), @@ -62,8 +51,7 @@ pub(crate) fn mul(x: T, y: T) -> FuncReturn { }) } // Checked divide -#[cfg(not(feature = "unchecked"))] -pub(crate) fn div(x: T, y: T) -> FuncReturn +pub fn div(x: T, y: T) -> FuncReturn where T: Display + CheckedDiv + PartialEq + Zero, { @@ -83,8 +71,7 @@ where }) } // Checked negative - e.g. -(i32::MIN) will overflow i32::MAX -#[cfg(not(feature = "unchecked"))] -pub(crate) fn neg(x: T) -> FuncReturn { +pub fn neg(x: T) -> FuncReturn { x.checked_neg().ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( format!("Negation overflow: -{}", x), @@ -93,8 +80,7 @@ pub(crate) fn neg(x: T) -> FuncReturn { }) } // Checked absolute -#[cfg(not(feature = "unchecked"))] -pub(crate) fn abs(x: T) -> FuncReturn { +pub fn abs(x: T) -> FuncReturn { // FIX - We don't use Signed::abs() here because, contrary to documentation, it panics // when the number is ::MIN instead of returning ::MIN itself. if x >= ::zero() { @@ -109,32 +95,26 @@ pub(crate) fn abs(x: T) -> FuncRetu } } // Unchecked add - may panic on overflow -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn add_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x + y) } // Unchecked subtract - may panic on underflow -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn sub_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x - y) } // Unchecked multiply - may panic on overflow -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn mul_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x * y) } // Unchecked divide - may panic when dividing by zero -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn div_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x / y) } // Unchecked negative - may panic on overflow -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn neg_u(x: T) -> FuncReturn<::Output> { Ok(-x) } // Unchecked absolute - may panic on overflow -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn abs_u(x: T) -> FuncReturn<::Output> where T: Neg + PartialOrd + Default + Into<::Output>, @@ -147,24 +127,17 @@ where } } // Bit operators -#[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "only_i64"))] fn binary_and(x: T, y: T) -> FuncReturn<::Output> { Ok(x & y) } -#[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "only_i64"))] fn binary_or(x: T, y: T) -> FuncReturn<::Output> { Ok(x | y) } -#[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "only_i64"))] fn binary_xor(x: T, y: T) -> FuncReturn<::Output> { Ok(x ^ y) } // Checked left-shift -#[cfg(not(feature = "unchecked"))] -pub(crate) fn shl(x: T, y: INT) -> FuncReturn { +pub fn shl(x: T, y: INT) -> FuncReturn { // Cannot shift by a negative number of bits if y < 0 { return Err(Box::new(EvalAltResult::ErrorArithmetic( @@ -181,8 +154,7 @@ pub(crate) fn shl(x: T, y: INT) -> FuncReturn { }) } // Checked right-shift -#[cfg(not(feature = "unchecked"))] -pub(crate) fn shr(x: T, y: INT) -> FuncReturn { +pub fn shr(x: T, y: INT) -> FuncReturn { // Cannot shift by a negative number of bits if y < 0 { return Err(Box::new(EvalAltResult::ErrorArithmetic( @@ -199,18 +171,15 @@ pub(crate) fn shr(x: T, y: INT) -> FuncReturn { }) } // Unchecked left-shift - may panic if shifting by a negative number of bits -#[cfg(feature = "unchecked")] -pub(crate) fn shl_u>(x: T, y: T) -> FuncReturn<>::Output> { +pub fn shl_u>(x: T, y: T) -> FuncReturn<>::Output> { Ok(x.shl(y)) } // Unchecked right-shift - may panic if shifting by a negative number of bits -#[cfg(feature = "unchecked")] -pub(crate) fn shr_u>(x: T, y: T) -> FuncReturn<>::Output> { +pub fn shr_u>(x: T, y: T) -> FuncReturn<>::Output> { Ok(x.shr(y)) } // Checked modulo -#[cfg(not(feature = "unchecked"))] -pub(crate) fn modulo(x: T, y: T) -> FuncReturn { +pub fn modulo(x: T, y: T) -> FuncReturn { x.checked_rem(&y).ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( format!("Modulo division by zero or overflow: {} % {}", x, y), @@ -219,62 +188,58 @@ pub(crate) fn modulo(x: T, y: T) -> FuncReturn { }) } // Unchecked modulo - may panic if dividing by zero -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn modulo_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x % y) } // Checked power -#[cfg(not(feature = "unchecked"))] -pub(crate) fn pow_i_i(x: INT, y: INT) -> FuncReturn { - #[cfg(not(feature = "only_i32"))] - if y > (u32::MAX as INT) { - Err(Box::new(EvalAltResult::ErrorArithmetic( - format!("Integer raised to too large an index: {} ~ {}", x, y), - Position::none(), - ))) - } else if y < 0 { - Err(Box::new(EvalAltResult::ErrorArithmetic( - format!("Integer raised to a negative index: {} ~ {}", x, y), - Position::none(), - ))) - } else { - x.checked_pow(y as u32).ok_or_else(|| { - Box::new(EvalAltResult::ErrorArithmetic( - format!("Power overflow: {} ~ {}", x, y), +pub fn pow_i_i(x: INT, y: INT) -> FuncReturn { + if cfg!(not(feature = "only_i32")) { + if y > (u32::MAX as INT) { + Err(Box::new(EvalAltResult::ErrorArithmetic( + format!("Integer raised to too large an index: {} ~ {}", x, y), Position::none(), - )) - }) - } - - #[cfg(feature = "only_i32")] - if y < 0 { - Err(Box::new(EvalAltResult::ErrorArithmetic( - format!("Integer raised to a negative index: {} ~ {}", x, y), - Position::none(), - ))) - } else { - x.checked_pow(y as u32).ok_or_else(|| { - Box::new(EvalAltResult::ErrorArithmetic( - format!("Power overflow: {} ~ {}", x, y), + ))) + } else if y < 0 { + Err(Box::new(EvalAltResult::ErrorArithmetic( + format!("Integer raised to a negative index: {} ~ {}", x, y), Position::none(), - )) - }) + ))) + } else { + x.checked_pow(y as u32).ok_or_else(|| { + Box::new(EvalAltResult::ErrorArithmetic( + format!("Power overflow: {} ~ {}", x, y), + Position::none(), + )) + }) + } + } else { + if y < 0 { + Err(Box::new(EvalAltResult::ErrorArithmetic( + format!("Integer raised to a negative index: {} ~ {}", x, y), + Position::none(), + ))) + } else { + x.checked_pow(y as u32).ok_or_else(|| { + Box::new(EvalAltResult::ErrorArithmetic( + format!("Power overflow: {} ~ {}", x, y), + Position::none(), + )) + }) + } } } // Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX) -#[cfg(feature = "unchecked")] -pub(crate) fn pow_i_i_u(x: INT, y: INT) -> FuncReturn { +pub fn pow_i_i_u(x: INT, y: INT) -> FuncReturn { Ok(x.pow(y as u32)) } // Floating-point power - always well-defined #[cfg(not(feature = "no_float"))] -pub(crate) fn pow_f_f(x: FLOAT, y: FLOAT) -> FuncReturn { +pub fn pow_f_f(x: FLOAT, y: FLOAT) -> FuncReturn { Ok(x.powf(y)) } // Checked power #[cfg(not(feature = "no_float"))] -#[cfg(not(feature = "unchecked"))] -pub(crate) fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn { +pub fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn { // Raise to power that is larger than an i32 if y > (i32::MAX as INT) { return Err(Box::new(EvalAltResult::ErrorArithmetic( @@ -286,9 +251,8 @@ pub(crate) fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn { Ok(x.powi(y as i32)) } // Unchecked power - may be incorrect if the power index is too high (> i32::MAX) -#[cfg(feature = "unchecked")] #[cfg(not(feature = "no_float"))] -pub(crate) fn pow_f_i_u(x: FLOAT, y: INT) -> FuncReturn { +pub fn pow_f_i_u(x: FLOAT, y: INT) -> FuncReturn { Ok(x.powi(y as i32)) } @@ -317,11 +281,8 @@ macro_rules! reg_sign { } def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { - #[cfg(not(feature = "unchecked"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { + if cfg!(not(feature = "unchecked")) { // Checked basic arithmetic reg_op!(lib, "+", add, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, u32, u64); @@ -332,8 +293,7 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { reg_op!(lib, ">>", shr, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "%", modulo, i8, u8, i16, u16, i32, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "+", add, i128, u128); reg_op!(lib, "-", sub, i128, u128); reg_op!(lib, "*", mul, i128, u128); @@ -345,8 +305,7 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { } } - #[cfg(feature = "unchecked")] - { + if cfg!(feature = "unchecked") { // Unchecked basic arithmetic reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, u32, u64); @@ -357,8 +316,7 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { reg_op!(lib, ">>", shr_u, i64, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "%", modulo_u, i8, u8, i16, u16, i32, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "+", add_u, i128, u128); reg_op!(lib, "-", sub_u, i128, u128); reg_op!(lib, "*", mul_u, i128, u128); @@ -372,13 +330,13 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { reg_sign!(lib, "sign", INT, i8, i16, i32, i64); - #[cfg(not(target_arch = "wasm32"))] - reg_sign!(lib, "sign", INT, i128); + if cfg!(not(target_arch = "wasm32")) { + reg_sign!(lib, "sign", INT, i128); + } } // Basic arithmetic for floating-point - no need to check - #[cfg(not(feature = "no_float"))] - { + if cfg!(not(feature = "no_float")) { reg_op!(lib, "+", add_u, f32); reg_op!(lib, "-", sub_u, f32); reg_op!(lib, "*", mul_u, f32); @@ -387,15 +345,12 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { reg_sign!(lib, "sign", f64, f64); } - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_op!(lib, "|", binary_or, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "&", binary_and, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "^", binary_xor, i8, u8, i16, u16, i32, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "|", binary_or, i128, u128); reg_op!(lib, "&", binary_and, i128, u128); reg_op!(lib, "^", binary_xor, i128, u128); @@ -405,12 +360,11 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { #[cfg(not(feature = "no_float"))] { // Checked power - #[cfg(not(feature = "unchecked"))] - lib.set_fn_2("~", pow_f_i); - - // Unchecked power - #[cfg(feature = "unchecked")] - lib.set_fn_2("~", pow_f_i_u); + if cfg!(not(feature = "unchecked")) { + lib.set_fn_2("~", pow_f_i); + } else { + lib.set_fn_2("~", pow_f_i_u); + } // Floating-point modulo and power reg_op!(lib, "%", modulo_u, f32); @@ -421,19 +375,15 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { } // Checked unary - #[cfg(not(feature = "unchecked"))] - { + if cfg!(not(feature = "unchecked")) { reg_unary!(lib, "-", neg, INT); reg_unary!(lib, "abs", abs, INT); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_unary!(lib, "-", neg, i8, i16, i32, i64); reg_unary!(lib, "abs", abs, i8, i16, i32, i64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_unary!(lib, "-", neg, i128); reg_unary!(lib, "abs", abs, i128); } @@ -441,19 +391,15 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { } // Unchecked unary - #[cfg(feature = "unchecked")] - { + if cfg!(feature = "unchecked") { reg_unary!(lib, "-", neg_u, INT); reg_unary!(lib, "abs", abs_u, INT); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_unary!(lib, "-", neg_u, i8, i16, i32, i64); reg_unary!(lib, "abs", abs_u, i8, i16, i32, i64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_unary!(lib, "-", neg_u, i128); reg_unary!(lib, "abs", abs_u, i128); } diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index c0bea7ee..5db525c5 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -82,7 +82,6 @@ macro_rules! reg_pad { }; } -#[cfg(not(feature = "no_index"))] def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { reg_op!(lib, "push", push, INT, bool, char, ImmutableString, Array, ()); reg_pad!(lib, "pad", pad, INT, bool, char, ImmutableString, Array, ()); @@ -104,15 +103,12 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { }, ); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_op!(lib, "push", push, i8, u8, i16, u16, i32, i64, u32, u64); reg_pad!(lib, "pad", pad, i8, u8, i16, u16, i32, u32, i64, u64); reg_tri!(lib, "insert", ins, i8, u8, i16, u16, i32, i64, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "push", push, i128, u128); reg_pad!(lib, "pad", pad, i128, u128); reg_tri!(lib, "insert", ins, i128, u128); diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index cccf4b6c..8914c6a5 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -73,9 +73,7 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { reg_range::(lib); lib.set_fn_2("range", get_range::); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { macro_rules! reg_range { ($lib:expr, $x:expr, $( $y:ty ),*) => ( $( @@ -87,16 +85,15 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { reg_range!(lib, "range", i8, u8, i16, u16, i32, i64, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - reg_range!(lib, "range", i128, u128); + if cfg!(not(target_arch = "wasm32")) { + reg_range!(lib, "range", i128, u128); + } } reg_step::(lib); lib.set_fn_3("range", get_step_range::); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { macro_rules! reg_step { ($lib:expr, $x:expr, $( $y:ty ),*) => ( $( @@ -108,7 +105,8 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { reg_step!(lib, "range", i8, u8, i16, u16, i32, i64, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - reg_step!(lib, "range", i128, u128); + if cfg!(not(target_arch = "wasm32")) { + reg_step!(lib, "range", i128, u128); + } } }); diff --git a/src/packages/logic.rs b/src/packages/logic.rs index 1cb4b437..6c70a38c 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -33,9 +33,7 @@ macro_rules! reg_op { } def_package!(crate:LogicPackage:"Logical operators.", lib, { - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_op!(lib, "<", lt, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "<=", lte, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, ">", gt, i8, u8, i16, u16, i32, u32, u64); @@ -43,8 +41,7 @@ def_package!(crate:LogicPackage:"Logical operators.", lib, { reg_op!(lib, "==", eq, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "!=", ne, i8, u8, i16, u16, i32, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "<", lt, i128, u128); reg_op!(lib, "<=", lte, i128, u128); reg_op!(lib, ">", gt, i128, u128); diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 9437bc67..4e2e58ad 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -1,25 +1,20 @@ #![cfg(not(feature = "no_object"))] +use crate::any::Dynamic; use crate::def_package; use crate::engine::Map; +use crate::module::FuncReturn; use crate::parser::{ImmutableString, INT}; -#[cfg(not(feature = "no_index"))] -use crate::{any::Dynamic, module::FuncReturn}; - -#[cfg(not(feature = "no_index"))] use crate::stdlib::vec::Vec; -#[cfg(not(feature = "no_index"))] fn map_get_keys(map: &mut Map) -> FuncReturn> { Ok(map.iter().map(|(k, _)| k.clone().into()).collect()) } -#[cfg(not(feature = "no_index"))] fn map_get_values(map: &mut Map) -> FuncReturn> { Ok(map.iter().map(|(_, v)| v.clone()).collect()) } -#[cfg(not(feature = "no_object"))] def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { lib.set_fn_2_mut( "has", @@ -74,9 +69,11 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { ); // Register map access functions - #[cfg(not(feature = "no_index"))] - lib.set_fn_1_mut("keys", map_get_keys); + if cfg!(not(feature = "no_index")) { + lib.set_fn_1_mut("keys", map_get_keys); + } - #[cfg(not(feature = "no_index"))] - lib.set_fn_1_mut("values", map_get_values); + if cfg!(not(feature = "no_index")) { + lib.set_fn_1_mut("values", map_get_values); + } }); diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index 55bb68a3..f8fd2d33 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -5,22 +5,20 @@ use crate::parser::INT; use crate::parser::FLOAT; #[cfg(not(feature = "no_float"))] -#[cfg(not(feature = "unchecked"))] use crate::{result::EvalAltResult, token::Position}; -#[cfg(not(feature = "no_float"))] #[cfg(feature = "no_std")] -use num_traits::*; +#[cfg(not(feature = "no_float"))] +use num_traits::float::Float; #[cfg(not(feature = "no_float"))] -#[cfg(not(feature = "unchecked"))] use crate::stdlib::{boxed::Box, format}; +#[allow(dead_code)] #[cfg(feature = "only_i32")] -#[cfg(not(feature = "unchecked"))] pub const MAX_INT: INT = i32::MAX; +#[allow(dead_code)] #[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "unchecked"))] pub const MAX_INT: INT = i64::MAX; def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { @@ -69,9 +67,7 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { lib.set_fn_1("to_float", |x: INT| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: f32| Ok(x as FLOAT)); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { lib.set_fn_1("to_float", |x: i8| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: u8| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: i16| Ok(x as FLOAT)); @@ -81,8 +77,7 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { lib.set_fn_1("to_float", |x: i64| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: u64| Ok(x as FLOAT)); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { lib.set_fn_1("to_float", |x: i128| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: u128| Ok(x as FLOAT)); } @@ -91,28 +86,25 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { lib.set_fn_1("to_int", |ch: char| Ok(ch as INT)); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { lib.set_fn_1("to_int", |x: i8| Ok(x as INT)); lib.set_fn_1("to_int", |x: u8| Ok(x as INT)); lib.set_fn_1("to_int", |x: i16| Ok(x as INT)); lib.set_fn_1("to_int", |x: u16| Ok(x as INT)); } - #[cfg(not(feature = "only_i32"))] - { + if cfg!(not(feature = "only_i32")) { lib.set_fn_1("to_int", |x: i32| Ok(x as INT)); lib.set_fn_1("to_int", |x: u64| Ok(x as INT)); - #[cfg(feature = "only_i64")] - lib.set_fn_1("to_int", |x: u32| Ok(x as INT)); + if cfg!(feature = "only_i64") { + lib.set_fn_1("to_int", |x: u32| Ok(x as INT)); + } } #[cfg(not(feature = "no_float"))] { - #[cfg(not(feature = "unchecked"))] - { + if cfg!(not(feature = "unchecked")) { lib.set_fn_1( "to_int", |x: f32| { @@ -141,8 +133,7 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { ); } - #[cfg(feature = "unchecked")] - { + if cfg!(feature = "unchecked") { lib.set_fn_1("to_int", |x: f32| Ok(x as INT)); lib.set_fn_1("to_int", |x: f64| Ok(x as INT)); } diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 2e405004..36f4f333 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -48,9 +48,7 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin reg_op!(lib, KEYWORD_DEBUG, to_debug, INT, bool, (), char, ImmutableString); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_op!(lib, KEYWORD_PRINT, to_string, i8, u8, i16, u16, i32, u32); reg_op!(lib, FN_TO_STRING, to_string, i8, u8, i16, u16, i32, u32); reg_op!(lib, KEYWORD_DEBUG, to_debug, i8, u8, i16, u16, i32, u32); @@ -58,8 +56,7 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin reg_op!(lib, FN_TO_STRING, to_string, i64, u64); reg_op!(lib, KEYWORD_DEBUG, to_debug, i64, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, KEYWORD_PRINT, to_string, i128, u128); reg_op!(lib, FN_TO_STRING, to_string, i128, u128); reg_op!(lib, KEYWORD_DEBUG, to_debug, i128, u128); diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 6ddc2b0c..35dcab00 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -94,14 +94,11 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str reg_op!(lib, "+", prepend, INT, bool, char); lib.set_fn_2("+", |_: (), y: ImmutableString| Ok(y)); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_op!(lib, "+", append, i8, u8, i16, u16, i32, i64, u32, u64); reg_op!(lib, "+", prepend, i8, u8, i16, u16, i32, i64, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "+", append, i128, u128); reg_op!(lib, "+", prepend, i128, u128); } @@ -228,7 +225,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str "pad", &[TypeId::of::(), TypeId::of::(), TypeId::of::()], |_engine: &Engine, _: &Module, args: &mut [&mut Dynamic]| { - let len = *args[1].downcast_ref::< INT>().unwrap(); + let len = *args[1].downcast_ref::().unwrap(); // Check if string will be over max size limit #[cfg(not(feature = "unchecked"))] From af2f8acb5dc42a9c63ad88f946a86637c746af94 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 1 Aug 2020 12:21:15 +0800 Subject: [PATCH 47/64] Refine indexer set. --- src/engine.rs | 64 +++++++++++++++++++++++++++----------------------- src/fn_call.rs | 48 ++++++++++++++++++------------------- 2 files changed, 58 insertions(+), 54 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index a011bf67..cb56c28b 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -721,8 +721,9 @@ impl Engine { Expr::Dot(x) | Expr::Index(x) => { let (idx, expr, pos) = x.as_ref(); let idx_pos = idx.position(); - let obj_ptr = &mut self - .get_indexed_mut(state, lib, target, idx_val, idx_pos, false, level)?; + let obj_ptr = &mut self.get_indexed_mut( + state, lib, target, idx_val, idx_pos, false, true, level, + )?; self.eval_dot_index_chain_helper( state, lib, this_ptr, obj_ptr, expr, idx_values, next_chain, level, @@ -735,14 +736,9 @@ impl Engine { let mut idx_val2 = idx_val.clone(); // `call_setter` is introduced to bypass double mutable borrowing of target - let mut call_setter = match self - .get_indexed_mut(state, lib, target, idx_val, pos, true, level) + let _call_setter = match self + .get_indexed_mut(state, lib, target, idx_val, pos, true, false, level) { - // Indexed value is an owned value - the only possibility is a value from an indexer. - // Try to call an index setter to update it back, but no need to raise an error - // if the indexer is read-only. - #[cfg(not(feature = "no_index"))] - Ok(obj_ptr) if obj_ptr.is_value() => Some((false, _new_val.unwrap())), // Indexed value is a reference - update directly Ok(ref mut obj_ptr) => { obj_ptr @@ -753,37 +749,42 @@ impl Engine { } Err(err) => match *err { // No index getter - try to call an index setter + #[cfg(not(feature = "no_index"))] EvalAltResult::ErrorIndexingType(_, _) => { // Raise error if there is no index getter nor setter - Some((true, _new_val.unwrap())) + Some(_new_val.unwrap()) } // Any other error - return err => return Err(Box::new(err)), }, }; - if let Some((must_have_setter, ref mut new_val)) = call_setter { - let args = &mut [target.as_mut(), &mut idx_val2, new_val]; + #[cfg(not(feature = "no_index"))] + if let Some(mut new_val) = _call_setter { + let val = target.as_mut(); + let val_type_name = val.type_name(); + let args = &mut [val, &mut idx_val2, &mut new_val]; - match self.exec_fn_call( + self.exec_fn_call( state, lib, FN_IDX_SET, 0, args, is_ref, true, false, None, None, level, - ) { - Ok(_) => (), - Err(err) if !must_have_setter => match *err { - // If there is no index setter, no need to set it back because the indexer is read-only - EvalAltResult::ErrorFunctionNotFound(_, _) => (), - _ => return Err(err), - }, - err => return err, - } + ) + .map_err(|err| match *err { + EvalAltResult::ErrorFunctionNotFound(_, _) => { + EvalAltResult::ErrorIndexingType( + self.map_type_name(val_type_name).into(), + Position::none(), + ) + } + err => err, + })?; } Ok(Default::default()) } // xxx[rhs] _ => self - .get_indexed_mut(state, lib, target, idx_val, pos, false, level) + .get_indexed_mut(state, lib, target, idx_val, pos, false, true, level) .map(|v| (v.clone_into_dynamic(), false)), } } @@ -806,8 +807,8 @@ impl Engine { Expr::Property(x) if target.is::() && _new_val.is_some() => { let ((prop, _, _), pos) = x.as_ref(); let index = prop.clone().into(); - let mut val = - self.get_indexed_mut(state, lib, target, index, *pos, true, level)?; + let mut val = self + .get_indexed_mut(state, lib, target, index, *pos, true, false, level)?; val.set_value(_new_val.unwrap()) .map_err(|err| err.new_position(rhs.position()))?; @@ -817,8 +818,9 @@ impl Engine { Expr::Property(x) if target.is::() => { let ((prop, _, _), pos) = x.as_ref(); let index = prop.clone().into(); - let val = - self.get_indexed_mut(state, lib, target, index, *pos, false, level)?; + let val = self.get_indexed_mut( + state, lib, target, index, *pos, false, false, level, + )?; Ok((val.clone_into_dynamic(), false)) } @@ -852,7 +854,9 @@ impl Engine { Expr::Property(p) => { let ((prop, _, _), pos) = p.as_ref(); let index = prop.clone().into(); - self.get_indexed_mut(state, lib, target, index, *pos, false, level)? + self.get_indexed_mut( + state, lib, target, index, *pos, false, true, level, + )? } // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr Expr::FnCall(x) if x.1.is_none() => { @@ -1125,6 +1129,7 @@ impl Engine { mut _idx: Dynamic, idx_pos: Position, _create: bool, + _indexers: bool, _level: usize, ) -> Result, Box> { self.inc_operations(state)?; @@ -1201,7 +1206,7 @@ impl Engine { #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] - _ => { + _ if _indexers => { let type_name = val.type_name(); let args = &mut [val, &mut _idx]; self.exec_fn_call( @@ -1216,7 +1221,6 @@ impl Engine { }) } - #[cfg(any(feature = "no_index", feature = "no_object"))] _ => Err(Box::new(EvalAltResult::ErrorIndexingType( self.map_type_name(val.type_name()).into(), Position::none(), diff --git a/src/fn_call.rs b/src/fn_call.rs index cbddcff3..05b65e3a 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -1188,30 +1188,30 @@ pub fn run_builtin_op_assignment( let mut x = x.write_lock::().unwrap(); let y = y.clone().cast::(); - #[cfg(not(feature = "unchecked"))] - match op { - "+=" => return Ok(Some(*x = add(*x, y)?)), - "-=" => return Ok(Some(*x = sub(*x, y)?)), - "*=" => return Ok(Some(*x = mul(*x, y)?)), - "/=" => return Ok(Some(*x = div(*x, y)?)), - "%=" => return Ok(Some(*x = modulo(*x, y)?)), - "~=" => return Ok(Some(*x = pow_i_i(*x, y)?)), - ">>=" => return Ok(Some(*x = shr(*x, y)?)), - "<<=" => return Ok(Some(*x = shl(*x, y)?)), - _ => (), - } - - #[cfg(feature = "unchecked")] - match op { - "+=" => return Ok(Some(*x += y)), - "-=" => return Ok(Some(*x -= y)), - "*=" => return Ok(Some(*x *= y)), - "/=" => return Ok(Some(*x /= y)), - "%=" => return Ok(Some(*x %= y)), - "~=" => return Ok(Some(*x = pow_i_i_u(*x, y)?)), - ">>=" => return Ok(Some(*x = shr_u(*x, y)?)), - "<<=" => return Ok(Some(*x = shl_u(*x, y)?)), - _ => (), + if cfg!(not(feature = "unchecked")) { + match op { + "+=" => return Ok(Some(*x = add(*x, y)?)), + "-=" => return Ok(Some(*x = sub(*x, y)?)), + "*=" => return Ok(Some(*x = mul(*x, y)?)), + "/=" => return Ok(Some(*x = div(*x, y)?)), + "%=" => return Ok(Some(*x = modulo(*x, y)?)), + "~=" => return Ok(Some(*x = pow_i_i(*x, y)?)), + ">>=" => return Ok(Some(*x = shr(*x, y)?)), + "<<=" => return Ok(Some(*x = shl(*x, y)?)), + _ => (), + } + } else { + match op { + "+=" => return Ok(Some(*x += y)), + "-=" => return Ok(Some(*x -= y)), + "*=" => return Ok(Some(*x *= y)), + "/=" => return Ok(Some(*x /= y)), + "%=" => return Ok(Some(*x %= y)), + "~=" => return Ok(Some(*x = pow_i_i_u(*x, y)?)), + ">>=" => return Ok(Some(*x = shr_u(*x, y)?)), + "<<=" => return Ok(Some(*x = shl_u(*x, y)?)), + _ => (), + } } match op { From cc53b2173111c644b22397776a148b801c82307a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 1 Aug 2020 22:28:13 +0800 Subject: [PATCH 48/64] Avoid hard-coding variable type for shared. --- src/any.rs | 123 +++++++++++++++++------------------------------- src/fn_call.rs | 10 ++-- src/utils.rs | 12 +++++ tests/shared.rs | 12 +++++ 4 files changed, 71 insertions(+), 86 deletions(-) diff --git a/src/any.rs b/src/any.rs index a8ee1e66..6280e426 100644 --- a/src/any.rs +++ b/src/any.rs @@ -160,18 +160,7 @@ pub enum Union { FnPtr(Box), Variant(Box>), #[cfg(not(feature = "no_shared"))] - Shared(Box), -} - -/// Internal Shared Dynamic representation. -/// -/// Created with `Dynamic::into_shared()`. -#[cfg(not(feature = "no_shared"))] -#[derive(Clone)] -pub struct SharedCell { - value_type_id: TypeId, - value_type_name: &'static str, - container: SharedMut, + Shared(SharedMut), } /// Underlying `Variant` read guard for `Dynamic`. @@ -202,7 +191,7 @@ impl<'d, T: Variant + Clone> Deref for DynamicReadLock<'d, T> { #[inline(always)] fn deref(&self) -> &Self::Target { match &self.0 { - DynamicReadLockInner::Reference(reference) => reference.deref(), + DynamicReadLockInner::Reference(reference) => *reference, // Unwrapping is safe because all checking is already done in its constructor #[cfg(not(feature = "no_shared"))] DynamicReadLockInner::Guard(guard) => guard.downcast_ref().unwrap(), @@ -238,7 +227,7 @@ impl<'d, T: Variant + Clone> Deref for DynamicWriteLock<'d, T> { #[inline(always)] fn deref(&self) -> &Self::Target { match &self.0 { - DynamicWriteLockInner::Reference(reference) => reference.deref(), + DynamicWriteLockInner::Reference(reference) => *reference, // Unwrapping is safe because all checking is already done in its constructor #[cfg(not(feature = "no_shared"))] DynamicWriteLockInner::Guard(guard) => guard.downcast_ref().unwrap(), @@ -250,7 +239,7 @@ impl<'d, T: Variant + Clone> DerefMut for DynamicWriteLock<'d, T> { #[inline(always)] fn deref_mut(&mut self) -> &mut Self::Target { match &mut self.0 { - DynamicWriteLockInner::Reference(reference) => reference.deref_mut(), + DynamicWriteLockInner::Reference(reference) => *reference, // Unwrapping is safe because all checking is already done in its constructor #[cfg(not(feature = "no_shared"))] DynamicWriteLockInner::Guard(guard) => guard.downcast_mut().unwrap(), @@ -309,7 +298,11 @@ impl Dynamic { Union::FnPtr(_) => TypeId::of::(), Union::Variant(value) => (***value).type_id(), #[cfg(not(feature = "no_shared"))] - Union::Shared(cell) => (**cell).value_type_id, + #[cfg(not(feature = "sync"))] + Union::Shared(cell) => (*cell.borrow()).type_id(), + #[cfg(not(feature = "no_shared"))] + #[cfg(feature = "sync")] + Union::Shared(cell) => (*cell.read().unwrap()).type_id(), } } @@ -333,7 +326,11 @@ impl Dynamic { Union::Variant(value) if value.is::() => "timestamp", Union::Variant(value) => (***value).type_name(), #[cfg(not(feature = "no_shared"))] - Union::Shared(cell) => (**cell).value_type_name, + #[cfg(not(feature = "sync"))] + Union::Shared(cell) => (*cell.borrow()).type_name(), + #[cfg(not(feature = "no_shared"))] + #[cfg(feature = "sync")] + Union::Shared(cell) => (*cell.read().unwrap()).type_name(), } } } @@ -390,7 +387,7 @@ impl fmt::Display for Dynamic { Union::Variant(value) if value.is::() => write!(f, ""), Union::Variant(value) => write!(f, "{}", (*value).type_name()), #[cfg(not(feature = "no_shared"))] - Union::Shared(cell) => write!(f, "", (**cell).value_type_name), + Union::Shared(_) => f.write_str(""), } } } @@ -418,7 +415,7 @@ impl fmt::Debug for Dynamic { Union::Variant(value) if value.is::() => write!(f, ""), Union::Variant(value) => write!(f, "{}", (*value).type_name()), #[cfg(not(feature = "no_shared"))] - Union::Shared(cell) => write!(f, "", (**cell).value_type_name), + Union::Shared(_) => f.write_str(""), } } } @@ -569,15 +566,10 @@ impl Dynamic { #[cfg(not(feature = "no_shared"))] return match self.0 { Union::Shared(..) => self, - _ => Self(Union::Shared(Box::new(SharedCell { - value_type_id: self.type_id(), - value_type_name: self.type_name(), - - #[cfg(not(feature = "sync"))] - container: Rc::new(RefCell::new(self)), - #[cfg(feature = "sync")] - container: Arc::new(RwLock::new(self)), - }))), + #[cfg(not(feature = "sync"))] + _ => Self(Union::Shared(Rc::new(RefCell::new(self)))), + #[cfg(feature = "sync")] + _ => Self(Union::Shared(Arc::new(RwLock::new(self)))), }; #[cfg(feature = "no_shared")] @@ -614,13 +606,11 @@ impl Dynamic { match self.0 { #[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "sync"))] - Union::Shared(cell) => return cell.container.borrow().deref().clone().try_cast(), + Union::Shared(cell) => return cell.borrow().clone().try_cast(), #[cfg(not(feature = "no_shared"))] #[cfg(feature = "sync")] - Union::Shared(cell) => { - return cell.container.read().unwrap().deref().clone().try_cast() - } + Union::Shared(cell) => return cell.read().unwrap().clone().try_cast(), _ => (), } @@ -752,32 +742,11 @@ impl Dynamic { match self.0 { #[cfg(not(feature = "no_shared"))] Union::Shared(ref cell) => { - let type_id = TypeId::of::(); - - if type_id != TypeId::of::() && type_id != cell.value_type_id { - return None; - } - #[cfg(not(feature = "sync"))] - return Some( - cell.container - .borrow() - .deref() - .downcast_ref::() - .unwrap() - .clone(), - ); + return Some(cell.borrow().downcast_ref::().unwrap().clone()); #[cfg(feature = "sync")] - return Some( - cell.container - .read() - .unwrap() - .deref() - .downcast_ref::() - .unwrap() - .clone(), - ); + return Some(cell.read().unwrap().downcast_ref::().unwrap().clone()); } _ => self.downcast_ref().cloned(), } @@ -792,20 +761,12 @@ impl Dynamic { match self.0 { #[cfg(not(feature = "no_shared"))] Union::Shared(ref cell) => { - let type_id = TypeId::of::(); - - if type_id != TypeId::of::() && type_id != cell.value_type_id { - return None; - } - #[cfg(not(feature = "sync"))] - return Some(DynamicReadLock(DynamicReadLockInner::Guard( - cell.container.borrow(), - ))); + return Some(DynamicReadLock(DynamicReadLockInner::Guard(cell.borrow()))); #[cfg(feature = "sync")] return Some(DynamicReadLock(DynamicReadLockInner::Guard( - cell.container.read().unwrap(), + cell.read().unwrap(), ))); } _ => self @@ -823,20 +784,14 @@ impl Dynamic { match self.0 { #[cfg(not(feature = "no_shared"))] Union::Shared(ref cell) => { - let type_id = TypeId::of::(); - - if type_id != TypeId::of::() && cell.value_type_id != type_id { - return None; - } - #[cfg(not(feature = "sync"))] return Some(DynamicWriteLock(DynamicWriteLockInner::Guard( - cell.container.borrow_mut(), + cell.borrow_mut(), ))); #[cfg(feature = "sync")] return Some(DynamicWriteLock(DynamicWriteLockInner::Guard( - cell.container.write().unwrap(), + cell.write().unwrap(), ))); } _ => self @@ -1086,16 +1041,22 @@ impl Dynamic { #[cfg(not(feature = "no_shared"))] Union::Shared(cell) => { #[cfg(not(feature = "sync"))] - match &cell.container.borrow().deref().0 { - Union::Str(s) => Ok(s.clone()), - Union::FnPtr(f) => Ok(f.clone().take_data().0), - _ => Err(cell.value_type_name), + { + let inner = cell.borrow(); + match &inner.0 { + Union::Str(s) => Ok(s.clone()), + Union::FnPtr(f) => Ok(f.clone().take_data().0), + _ => Err((*inner).type_name()), + } } #[cfg(feature = "sync")] - match &cell.container.read().unwrap().deref().0 { - Union::Str(s) => Ok(s.clone()), - Union::FnPtr(f) => Ok(f.clone().take_data().0), - _ => Err(cell.value_type_name), + { + let inner = cell.read().unwrap(); + match &inner.0 { + Union::Str(s) => Ok(s.clone()), + Union::FnPtr(f) => Ok(f.clone().take_data().0), + _ => Err((*inner).type_name()), + } } } _ => Err(self.type_name()), diff --git a/src/fn_call.rs b/src/fn_call.rs index 05b65e3a..ace08128 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -1185,8 +1185,8 @@ pub fn run_builtin_op_assignment( } if args_type == TypeId::of::() { - let mut x = x.write_lock::().unwrap(); let y = y.clone().cast::(); + let mut x = x.write_lock::().unwrap(); if cfg!(not(feature = "unchecked")) { match op { @@ -1221,8 +1221,8 @@ pub fn run_builtin_op_assignment( _ => (), } } else if args_type == TypeId::of::() { - let mut x = x.write_lock::().unwrap(); let y = y.clone().cast::(); + let mut x = x.write_lock::().unwrap(); match op { "&=" => return Ok(Some(*x = *x && y)), @@ -1230,19 +1230,19 @@ pub fn run_builtin_op_assignment( _ => (), } } else if args_type == TypeId::of::() { + let y = y.read_lock::().unwrap().deref().clone(); let mut x = x.write_lock::().unwrap(); - let y = y.read_lock::().unwrap(); match op { - "+=" => return Ok(Some(*x += y.deref())), + "+=" => return Ok(Some(*x += y)), _ => (), } } #[cfg(not(feature = "no_float"))] if args_type == TypeId::of::() { - let mut x = x.write_lock::().unwrap(); let y = y.clone().cast::(); + let mut x = x.write_lock::().unwrap(); match op { "+=" => return Ok(Some(*x += y)), diff --git a/src/utils.rs b/src/utils.rs index 6668a7a9..ee448260 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -264,6 +264,18 @@ impl AddAssign<&ImmutableString> for ImmutableString { } } +impl AddAssign for ImmutableString { + fn add_assign(&mut self, rhs: ImmutableString) { + if !rhs.is_empty() { + if self.is_empty() { + self.0 = rhs.0; + } else { + self.make_mut().push_str(rhs.0.as_str()); + } + } + } +} + impl Add<&str> for ImmutableString { type Output = Self; diff --git a/tests/shared.rs b/tests/shared.rs index 7cd7af86..8223c876 100644 --- a/tests/shared.rs +++ b/tests/shared.rs @@ -11,11 +11,23 @@ fn test_shared() -> Result<(), Box> { assert_eq!(engine.eval::("shared(true)")?, true); + assert_eq!( + engine.eval::("let x = shared(true); type_of(x)")?, + "bool" + ); + + assert_eq!( + engine.eval::("let x = shared(true); x = (); type_of(x)")?, + "()" + ); + #[cfg(not(feature = "no_float"))] assert_eq!(engine.eval::("shared(4.2)")?, 4.2); assert_eq!(engine.eval::(r#"shared("test")"#)?, "test"); + assert_eq!(engine.eval::(r#"shared("test")"#)?, "test"); + assert_eq!(engine.eval::("shared('x')")?, 'x'); assert!(engine.eval::("is_shared(shared(42))")?); From 1daf91df30038bb2a0b00132b27ca4e4ff06db7b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 1 Aug 2020 22:28:17 +0800 Subject: [PATCH 49/64] Avoid storing shared value inside shared value. --- src/engine.rs | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index cb56c28b..24a1b20b 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1347,7 +1347,11 @@ impl Engine { // Normal assignment ScopeEntryType::Normal if op.is_empty() => { if cfg!(not(feature = "no_shared")) && lhs_ptr.is_shared() { - *lhs_ptr.write_lock::().unwrap() = rhs_val; + *lhs_ptr.write_lock::().unwrap() = if rhs_val.is_shared() { + rhs_val.clone_inner_data().unwrap() + } else { + rhs_val + }; } else { *lhs_ptr = rhs_val; } @@ -1368,19 +1372,16 @@ impl Engine { .get_fn(hash_fn, false) .or_else(|| self.packages.get_fn(hash_fn, false)) { - #[cfg(not(feature = "no_shared"))] - let mut lock_guard; + if cfg!(not(feature = "no_shared")) && lhs_ptr.is_shared() { + let mut lock_guard = lhs_ptr.write_lock::().unwrap(); + let lhs_ptr_inner = lock_guard.deref_mut(); - #[cfg(not(feature = "no_shared"))] - let lhs_ptr = if lhs_ptr.is_shared() { - lock_guard = Some(lhs_ptr.write_lock::().unwrap()); - lock_guard.as_deref_mut().unwrap() + // Overriding exact implementation + func(self, lib, &mut [lhs_ptr_inner, &mut rhs_val])?; } else { - lhs_ptr - }; - - // Overriding exact implementation - func(self, lib, &mut [lhs_ptr, &mut rhs_val])?; + // Overriding exact implementation + func(self, lib, &mut [lhs_ptr, &mut rhs_val])?; + } } else if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_none() { // Not built in, map to `var = var op rhs` let op = &op[..op.len() - 1]; // extract operator without = @@ -1396,7 +1397,11 @@ impl Engine { .map_err(|err| err.new_position(*op_pos))?; if cfg!(not(feature = "no_shared")) && lhs_ptr.is_shared() { - *lhs_ptr.write_lock::().unwrap() = value; + *lhs_ptr.write_lock::().unwrap() = if value.is_shared() { + value.clone_inner_data().unwrap() + } else { + value + }; } else { *lhs_ptr = value; } From b86c87253bc0137a0d8e9a03799bc811e6710e03 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 2 Aug 2020 13:33:51 +0800 Subject: [PATCH 50/64] Prevent data races. --- src/any.rs | 32 ++++++++++++++++++++++++++++++++ src/api.rs | 6 ++++++ src/engine.rs | 15 ++++++++++----- src/fn_call.rs | 30 ++++++++++++++++++++++++++++++ src/result.rs | 8 +++++++- tests/shared.rs | 39 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 124 insertions(+), 6 deletions(-) diff --git a/src/any.rs b/src/any.rs index 6280e426..e0281efe 100644 --- a/src/any.rs +++ b/src/any.rs @@ -282,6 +282,11 @@ impl Dynamic { } /// Get the TypeId of the value held by this `Dynamic`. + /// + /// # Panics and Deadlocks When Value is Shared + /// + /// Under the `sync` feature, this call may deadlock. + /// Otherwise, this call panics if the data is currently borrowed for write. pub fn type_id(&self) -> TypeId { match &self.0 { Union::Unit(_) => TypeId::of::<()>(), @@ -307,6 +312,11 @@ impl Dynamic { } /// Get the name of the type of the value held by this `Dynamic`. + /// + /// # Panics and Deadlocks When Value is Shared + /// + /// Under the `sync` feature, this call may deadlock. + /// Otherwise, this call panics if the data is currently borrowed for write. pub fn type_name(&self) -> &'static str { match &self.0 { Union::Unit(_) => "()", @@ -752,6 +762,28 @@ impl Dynamic { } } + /// Is the `Dynamic` a shared value that is locked? + /// + /// ## Note + /// + /// Under the `sync` feature, shared values use `RwLock` and they are never locked. + /// Access just waits until the `RwLock` is released. + /// So this method always returns `false` under `sync`. + #[inline(always)] + pub fn is_locked(&self) -> bool { + match self.0 { + #[cfg(not(feature = "no_shared"))] + Union::Shared(ref _cell) => { + #[cfg(not(feature = "sync"))] + return _cell.try_borrow().is_err(); + + #[cfg(feature = "sync")] + return false; + } + _ => false, + } + } + /// Get a reference of a specific type to the `Dynamic`. /// Casting to `Dynamic` just returns a reference to it. /// diff --git a/src/api.rs b/src/api.rs index e6e9ea7a..be69f53d 100644 --- a/src/api.rs +++ b/src/api.rs @@ -3,6 +3,7 @@ use crate::any::{Dynamic, Variant}; use crate::engine::{Engine, Imports, State}; use crate::error::ParseError; +use crate::fn_call::ensure_no_data_race; use crate::fn_native::{IteratorFn, SendSync}; use crate::module::{FuncReturn, Module}; use crate::optimize::{optimize_into_ast, OptimizationLevel}; @@ -1282,6 +1283,11 @@ impl Engine { let mut mods = Imports::new(); let args = args.as_mut(); + // Check for data race. + if cfg!(not(feature = "no_shared")) { + ensure_no_data_race(name, args, false)?; + } + self.call_script_fn( scope, &mut mods, &mut state, lib, this_ptr, name, fn_def, args, 0, ) diff --git a/src/engine.rs b/src/engine.rs index 24a1b20b..b0bd5e8b 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -41,14 +41,11 @@ use crate::stdlib::{ collections::{HashMap, HashSet}, fmt, format, iter::{empty, once}, + ops::DerefMut, string::{String, ToString}, vec::Vec, }; -#[cfg(not(feature = "no_shared"))] -#[cfg(not(feature = "no_object"))] -use crate::stdlib::ops::DerefMut; - #[cfg(not(feature = "no_index"))] use crate::stdlib::any::TypeId; @@ -561,7 +558,8 @@ pub fn search_imports_mut<'s>( }) } -/// Search for a variable within the scope and imports +/// Search for a variable within the scope or within imports, +/// depending on whether the variable name is qualified. pub fn search_namespace<'s, 'a>( scope: &'s mut Scope, mods: &'s mut Imports, @@ -631,6 +629,13 @@ pub fn search_scope_only<'s, 'a>( }; let (val, typ) = scope.get_mut(index); + + // Check for data race - probably not necessary because the only place it should conflict is in a method call + // when the object variable is also used as a parameter. + // if cfg!(not(feature = "no_shared")) && val.is_locked() { + // return Err(Box::new(EvalAltResult::ErrorDataRace(name.into(), *pos))); + // } + Ok((val, name, typ, *pos)) } diff --git a/src/fn_call.rs b/src/fn_call.rs index ace08128..2b4ffb65 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -160,6 +160,31 @@ fn add_captured_variables_into_scope<'s>( ); } +#[inline(always)] +pub fn ensure_no_data_race( + fn_name: &str, + args: &FnCallArgs, + is_ref: bool, +) -> Result<(), Box> { + if cfg!(not(feature = "no_shared")) { + let skip = if is_ref { 1 } else { 0 }; + + if let Some((n, _)) = args + .iter() + .skip(skip) + .enumerate() + .find(|(_, a)| a.is_locked()) + { + return Err(Box::new(EvalAltResult::ErrorDataRace( + format!("argument #{} of function '{}'", n + 1 + skip, fn_name), + Position::none(), + ))); + } + } + + Ok(()) +} + impl Engine { /// Call a native Rust function registered with the `Engine`. /// Position in `EvalAltResult` is `None` and must be set afterwards. @@ -430,6 +455,11 @@ impl Engine { def_val: Option, level: usize, ) -> Result<(Dynamic, bool), Box> { + // Check for data race. + if cfg!(not(feature = "no_shared")) { + ensure_no_data_race(fn_name, args, is_ref)?; + } + // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. let arg_types = args.iter().map(|a| a.type_id()); let hash_fn = calc_fn_hash(empty(), fn_name, args.len(), arg_types); diff --git a/src/result.rs b/src/result.rs index c0cc620d..cecd14bd 100644 --- a/src/result.rs +++ b/src/result.rs @@ -69,6 +69,8 @@ pub enum EvalAltResult { ErrorVariableNotFound(String, Position), /// Usage of an unknown module. Wrapped value is the name of the module. ErrorModuleNotFound(String, Position), + /// Data race detected when accessing a variable. Wrapped value is the name of the variable. + ErrorDataRace(String, Position), /// Assignment to an inappropriate LHS (left-hand-side) expression. ErrorAssignmentToUnknownLHS(Position), /// Assignment to a constant variable. @@ -136,7 +138,8 @@ impl EvalAltResult { Self::ErrorLogicGuard(_) => "Boolean value expected", Self::ErrorFor(_) => "For loop expects an array, object map, or range", Self::ErrorVariableNotFound(_, _) => "Variable not found", - Self::ErrorModuleNotFound(_, _) => "module not found", + Self::ErrorModuleNotFound(_, _) => "Module not found", + Self::ErrorDataRace(_, _) => "Data race detected when accessing variable", Self::ErrorAssignmentToUnknownLHS(_) => { "Assignment to an unsupported left-hand side expression" } @@ -180,6 +183,7 @@ impl fmt::Display for EvalAltResult { Self::ErrorFunctionNotFound(s, _) | Self::ErrorVariableNotFound(s, _) + | Self::ErrorDataRace(s, _) | Self::ErrorModuleNotFound(s, _) => write!(f, "{}: '{}'", desc, s)?, Self::ErrorDotExpr(s, _) if !s.is_empty() => write!(f, "{}", s)?, @@ -289,6 +293,7 @@ impl EvalAltResult { | Self::ErrorFor(pos) | Self::ErrorVariableNotFound(_, pos) | Self::ErrorModuleNotFound(_, pos) + | Self::ErrorDataRace(_, pos) | Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToConstant(_, pos) | Self::ErrorMismatchOutputType(_, _, pos) @@ -329,6 +334,7 @@ impl EvalAltResult { | Self::ErrorFor(pos) | Self::ErrorVariableNotFound(_, pos) | Self::ErrorModuleNotFound(_, pos) + | Self::ErrorDataRace(_, pos) | Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToConstant(_, pos) | Self::ErrorMismatchOutputType(_, _, pos) diff --git a/tests/shared.rs b/tests/shared.rs index 8223c876..b2238fbf 100644 --- a/tests/shared.rs +++ b/tests/shared.rs @@ -283,3 +283,42 @@ fn test_shared() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_shared_data_race() -> Result<(), Box> { + let engine = Engine::new(); + + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "no_object"))] + { + assert_eq!( + engine.eval::( + r#" + fn foo(x) { this += x }; + + let a = shared(41); + a.foo(1); + a + "# + )?, + 42 + ); + + assert!(matches!( + *engine + .eval::( + r#" + fn foo(x) { this += x }; + + let a = shared(42); + a.foo(a); + a + "# + ) + .expect_err("should error"), + EvalAltResult::ErrorDataRace(_, _) + )); + } + + Ok(()) +} From 747c0345f2ccff00a41bbcea3488b2689f11941d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 2 Aug 2020 13:51:07 +0800 Subject: [PATCH 51/64] Do not convert a function call into a method call if the object is shared. --- src/any.rs | 2 +- src/fn_call.rs | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/any.rs b/src/any.rs index e0281efe..08ebdeab 100644 --- a/src/any.rs +++ b/src/any.rs @@ -258,7 +258,7 @@ impl Dynamic { } /// Does this `Dynamic` hold a shared data type - /// instead of one of the support system primitive types? + /// instead of one of the supported system primitive types? pub fn is_shared(&self) -> bool { match self.0 { #[cfg(not(feature = "no_shared"))] diff --git a/src/fn_call.rs b/src/fn_call.rs index 2b4ffb65..6980caca 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -906,11 +906,11 @@ impl Engine { // No arguments args = Default::default(); } else { - // See if the first argument is a variable, if so, convert to method-call style + // If the first argument is a variable, and there is no curried arguments, convert to method-call style // in order to leverage potential &mut first argument and avoid cloning the value match args_expr.get(0).unwrap() { // func(x, ...) -> x.func(...) - lhs @ Expr::Variable(_) => { + lhs @ Expr::Variable(_) if curry.is_empty() => { arg_values = args_expr .iter() .skip(1) @@ -922,12 +922,14 @@ impl Engine { self.inc_operations(state) .map_err(|err| err.new_position(pos))?; - args = once(target) - .chain(curry.iter_mut()) - .chain(arg_values.iter_mut()) - .collect(); - - is_ref = true; + // Turn it into a method call only if the object is not shared + args = if target.is_shared() { + arg_values.insert(0, target.clone_inner_data().unwrap()); + arg_values.iter_mut().collect() + } else { + is_ref = true; + once(target).chain(arg_values.iter_mut()).collect() + }; } // func(..., ...) _ => { From 096eb4493e058988cc503d4e4fcbd2b769ac688f Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 2 Aug 2020 18:55:22 +0800 Subject: [PATCH 52/64] Engine::load_package API change. --- RELEASES.md | 1 + src/settings.rs | 4 ++-- tests/closures.rs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index f163f06f..d727a20e 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -33,6 +33,7 @@ Breaking changes * Function signature for defining custom syntax is simplified. * `Engine::register_raw_fn_XXX` API shortcuts are removed. * `PackagesCollection::get_fn`, `PackagesCollection::contains_fn`, `Module::get_fn` and `Module::contains_fn` now take an additional `public_only` parameter indicating whether only public functions are accepted. +* `Engine::load_package` takes any type that is `Into`. Housekeeping ------------ diff --git a/src/settings.rs b/src/settings.rs index d638499c..d4949a41 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -18,9 +18,9 @@ impl Engine { /// /// When searching for functions, packages loaded later are preferred. /// In other words, loaded packages are searched in reverse order. - pub fn load_package(&mut self, package: PackageLibrary) -> &mut Self { + pub fn load_package(&mut self, package: impl Into) -> &mut Self { // Push the package to the top - packages are searched in reverse order - self.packages.push(package); + self.packages.push(package.into()); self } diff --git a/tests/closures.rs b/tests/closures.rs index 3aec4865..27ecc5e6 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -16,7 +16,7 @@ fn test_fn_ptr_curry_call() -> Result<(), Box> { ); let mut engine = Engine::new(); - engine.load_package(module.into()); + engine.load_package(module); #[cfg(not(feature = "no_object"))] assert_eq!( From dd0177f588e4f940aa3b57f1771db27f926153d9 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 3 Aug 2020 10:07:52 +0800 Subject: [PATCH 53/64] Allow deprecated API. --- tests/call_fn.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 0f9087d2..a22f7b6d 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -88,6 +88,7 @@ fn test_call_fn_private() -> Result<(), Box> { fn test_fn_ptr_raw() -> Result<(), Box> { let mut engine = Engine::new(); + #[allow(deprecated)] engine .register_fn("mul", |x: &mut INT, y: INT| *x *= y) .register_raw_fn( From 4079164bfd2a4a7456f9468a3a398c49de5debe0 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 3 Aug 2020 12:10:20 +0800 Subject: [PATCH 54/64] Implement closures. --- .github/workflows/build.yml | 3 +- Cargo.toml | 5 +- RELEASES.md | 2 + doc/src/appendix/keywords.md | 3 +- doc/src/language/fn-anon.md | 14 +- doc/src/language/fn-closure.md | 22 ++- doc/src/language/keywords.md | 2 +- doc/src/language/oop.md | 19 +- doc/src/links.md | 3 +- doc/src/start/features.md | 5 +- examples/repl.rs | 13 +- src/any.rs | 146 ++++++++------- src/api.rs | 2 +- src/engine.rs | 66 ++++--- src/error.rs | 2 +- src/fn_call.rs | 46 ++--- src/fn_native.rs | 8 +- src/optimize.rs | 2 +- src/parser.rs | 92 +++++++--- src/scope.rs | 69 ++----- src/token.rs | 19 +- tests/closures.rs | 59 +++++- tests/functions.rs | 2 +- tests/shared.rs | 324 --------------------------------- 24 files changed, 340 insertions(+), 588 deletions(-) delete mode 100644 tests/shared.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 276cbf72..11216cc3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,8 +30,7 @@ jobs: - "--features no_object" - "--features no_function" - "--features no_module" - - "--features no_capture" - - "--features no_shared" + - "--features no_closure" - "--features unicode-xid-ident" toolchain: [stable] experimental: [false] diff --git a/Cargo.toml b/Cargo.toml index c450eecd..7d20a93c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,14 +33,13 @@ only_i64 = [] # set INT=i64 (default) and disable support for all other in no_index = [] # no arrays and indexing no_object = [] # no custom objects no_function = [] # no script-defined functions -no_capture = [] # no automatic read/write binding of anonymous function's local variables to it's external context -no_shared = [] # no shared values +no_closure = [] # no automatic sharing and capture of anonymous functions to external variables no_module = [] # no modules internals = [] # expose internal data structures unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. # compiling for no-std -no_std = [ "no_shared", "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ] +no_std = [ "no_closure", "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ] [profile.release] lto = "fat" diff --git a/RELEASES.md b/RELEASES.md index f163f06f..1679aa0c 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -25,6 +25,7 @@ New features * Capturing of the calling scope for function call via the `func!(...)` syntax. * `Module::set_indexer_get_set_fn` is added as a shorthand of both `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn`. * New `unicode-xid-ident` feature to allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) for identifiers. +* `Scope::iter_raw` returns an iterator with a reference to the underlying `Dynamic` value (which may be shared). Breaking changes ---------------- @@ -33,6 +34,7 @@ Breaking changes * Function signature for defining custom syntax is simplified. * `Engine::register_raw_fn_XXX` API shortcuts are removed. * `PackagesCollection::get_fn`, `PackagesCollection::contains_fn`, `Module::get_fn` and `Module::contains_fn` now take an additional `public_only` parameter indicating whether only public functions are accepted. +* The iterator returned by `Scope::iter` now contains a clone of the `Dynamic` value (unshared). Housekeeping ------------ diff --git a/doc/src/appendix/keywords.md b/doc/src/appendix/keywords.md index a1e20fed..d896739a 100644 --- a/doc/src/appendix/keywords.md +++ b/doc/src/appendix/keywords.md @@ -10,8 +10,6 @@ Keywords List | `let` | Variable declaration | | No | | `const` | Constant declaration | | No | | `is_shared` | Is a value shared? | | No | -| `shared` | Share value | [`no_shared`] | No | -| `take` | Un-share value | [`no_shared`] | No | | `if` | If statement | | No | | `else` | else block of if statement | | No | | `while` | While loop | | No | @@ -44,6 +42,7 @@ Reserved Keywords | --------- | --------------------- | | `var` | Variable declaration | | `static` | Variable declaration | +| `shared` | Share value | | `do` | Looping | | `each` | Looping | | `then` | Control flow | diff --git a/doc/src/language/fn-anon.md b/doc/src/language/fn-anon.md index e76ba59c..b8a9154d 100644 --- a/doc/src/language/fn-anon.md +++ b/doc/src/language/fn-anon.md @@ -22,7 +22,7 @@ fn print_obj() { print(this.data); } ``` The above can be replaced by using _anonymous functions_ which have the same syntax as Rust's closures -(but they are **NOT** closures, merely syntactic sugar): +(but they are **NOT** real closures, merely syntactic sugar): ```rust let obj = #{ @@ -50,12 +50,10 @@ fn anon_fn_1002() { print this.data; } ``` -WARNING - NOT Closures ----------------------- +WARNING - NOT Real Closures +-------------------------- Remember: anonymous functions, though having the same syntax as Rust _closures_, are themselves -**not** closures. In particular, they do not capture their execution environment. They are more like -Rust's function pointers. - -They do, however, _capture_ variable _values_ from their execution environment, unless the [`no_capture`] -feature is turned on. This is accomplished via [automatic currying][capture]. +**not** real closures. +In particular, they capture their execution environment via [automatic currying][capture], +unless the [`no_closure`] feature is turned on. diff --git a/doc/src/language/fn-closure.md b/doc/src/language/fn-closure.md index 1701cc4a..9ba66f2e 100644 --- a/doc/src/language/fn-closure.md +++ b/doc/src/language/fn-closure.md @@ -15,7 +15,7 @@ is created. Variables that are accessible during the time the [anonymous function] is created can be captured, as long as they are not shadowed by local variables defined within the function's scope. -The values captured are the values of those variables at the time of the [anonymous function]'s creation. +The captured variables are automatically converted into reference-counted shared values. New Parameters For Captured Variables @@ -29,28 +29,32 @@ In actual implementation, this de-sugars to: 3. The variable is added to the parameters list of the anonymous function, at the front. -4. The current value of the variable is then [curried][currying] into the [function pointer] itself, essentially carrying that value and inserting it into future calls of the function. +4. The variable is then turned into a reference-counted shared value. -Automatic currying can be turned off via the [`no_capture`] feature. +5. The shared value is then [curried][currying] into the [function pointer] itself, essentially carrying a reference to that shared value and inserting it into future calls of the function. + +Automatic currying can be turned off via the [`no_closure`] feature. Examples -------- ```rust -let x = 40; +let x = 1; -let f = |y| x + y; // current value of variable 'x' is auto-curried - // the value 40 is curried into 'f' +let f = |y| x + y; // variable 'x' is auto-curried (captured) into 'f' + // 'x' is converted into a shared value -x = 1; // 'x' can be changed but the curried value is not +x = 40; // 'x' can be changed -f.call(2) == 42; // the value of 'x' is still 40 +f.call(2) == 42; // the value of 'x' is 40 because 'x' is shared // The above de-sugars into this: fn anon$1001(x, y) { x + y } // parameter 'x' is inserted -let f = Fn("anon$1001").curry(x); // current value of 'x' is curried +make_shared(x); // convert 'x' into a shared value + +let f = Fn("anon$1001").curry(x); // shared 'x' is curried f.call(2) == 42; ``` diff --git a/doc/src/language/keywords.md b/doc/src/language/keywords.md index 0683386e..4ddaae8f 100644 --- a/doc/src/language/keywords.md +++ b/doc/src/language/keywords.md @@ -9,7 +9,7 @@ The following are reserved keywords in Rhai: | ------------------------------------------------- | ------------------------------------------------ | --------------------- | :--------------------: | | `true`, `false` | | Boolean constants | | | `let`, `const` | `var`, `static` | Variable declarations | | -| `shared`, `take`, `is_shared` | | Shared values | [`no_shared`] | +| `is_shared` | | Shared values | [`no_closure`] | | `if`, `else` | `then`, `goto`, `exit` | Control flow | | | | `switch`, `match`, `case` | Matching | | | `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | Looping | | diff --git a/doc/src/language/oop.md b/doc/src/language/oop.md index 09bc7b55..017c4932 100644 --- a/doc/src/language/oop.md +++ b/doc/src/language/oop.md @@ -30,26 +30,33 @@ that resembles very closely that of class methods in an OOP language. Anonymous functions can also _capture_ variables from the defining environment, which is a very common OOP pattern. Capturing is accomplished via a feature called _[automatic currying]_ and -can be turned off via the [`no_capture`] feature. +can be turned off via the [`no_closure`] feature. Examples -------- ```rust +let factor = 1; + // Define the object let obj = #{ data: 0, - increment: |x| this.data += x, // when called, 'this' binds to 'obj' - update: |x| this.data = x, // when called, 'this' binds to 'obj' - action: || print(this.data) // when called, 'this' binds to 'obj' + increment: |x| this.data += x, // 'this' binds to 'obj' + update: |x| this.data = x * factor, // 'this' binds to 'obj', 'factor' is captured + action: || print(this.data) // 'this' binds to 'obj' }; // Use the object obj.increment(1); -obj.action(); // prints 1 +obj.action(); // prints 1 obj.update(42); -obj.action(); // prints 42 +obj.action(); // prints 42 + +factor = 2; + +obj.update(42); +obj.action(); // prints 84 ``` diff --git a/doc/src/links.md b/doc/src/links.md index a22f0038..8667082c 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -9,8 +9,7 @@ [`no_object`]: {{rootUrl}}/start/features.md [`no_function`]: {{rootUrl}}/start/features.md [`no_module`]: {{rootUrl}}/start/features.md -[`no_capture`]: {{rootUrl}}/start/features.md -[`no_shared`]: {{rootUrl}}/start/features.md +[`no_closure`]: {{rootUrl}}/start/features.md [`no_std`]: {{rootUrl}}/start/features.md [`no-std`]: {{rootUrl}}/start/features.md [`internals`]: {{rootUrl}}/start/features.md diff --git a/doc/src/start/features.md b/doc/src/start/features.md index 9f0d42d4..3b8cee04 100644 --- a/doc/src/start/features.md +++ b/doc/src/start/features.md @@ -23,9 +23,8 @@ more control over what a script can (or cannot) do. | `no_object` | Disable support for [custom types] and [object maps]. | | `no_function` | Disable script-defined [functions]. | | `no_module` | Disable loading external [modules]. | -| `no_capture` | Disable [capturing][capture] external variables in [anonymous functions] and [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls. | -| `no_shared` | Disable sharing of data values. | -| `no_std` | Build for `no-std` (implies `no_shared`). Notice that additional dependencies will be pulled in to replace `std` features. | +| `no_closure` | Disable [capturing][capture] external variables in [anonymous functions] to simulate _closures_, or [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls. | +| `no_std` | Build for `no-std` (implies `no_closure`). Notice that additional dependencies will be pulled in to replace `std` features. | | `serde` | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. | | `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. | | `unicode-xid-ident` | Allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) as identifiers. | diff --git a/examples/repl.rs b/examples/repl.rs index b3877dd0..ee73c193 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -114,10 +114,15 @@ fn main() { } "exit" | "quit" => break, // quit "scope" => { - scope - .iter() - .enumerate() - .for_each(|(i, (name, value))| println!("[{}] {} = {:?}", i + 1, name, value)); + scope.iter_raw().enumerate().for_each(|(i, (name, value))| { + println!( + "[{}] {}{} = {:?}", + i + 1, + name, + if value.is_shared() { " (shared)" } else { "" }, + *value.read_lock::().unwrap(), + ) + }); continue; } "astu" => { diff --git a/src/any.rs b/src/any.rs index 08ebdeab..78d681be 100644 --- a/src/any.rs +++ b/src/any.rs @@ -4,7 +4,7 @@ use crate::fn_native::{FnPtr, SendSync}; use crate::parser::{ImmutableString, INT}; use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast}; -#[cfg(not(feature = "no_shared"))] +#[cfg(not(feature = "no_closure"))] use crate::fn_native::SharedMut; #[cfg(not(feature = "no_float"))] @@ -24,14 +24,14 @@ use crate::stdlib::{ string::String, }; -#[cfg(not(feature = "no_shared"))] +#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] use crate::stdlib::{ cell::{Ref, RefCell, RefMut}, rc::Rc, }; -#[cfg(not(feature = "no_shared"))] +#[cfg(not(feature = "no_closure"))] #[cfg(feature = "sync")] use crate::stdlib::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; @@ -159,7 +159,7 @@ pub enum Union { Map(Box), FnPtr(Box), Variant(Box>), - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] Shared(SharedMut), } @@ -176,11 +176,11 @@ enum DynamicReadLockInner<'d, T: Variant + Clone> { /// A simple reference to a non-shared value. Reference(&'d T), /// A read guard to a shared `RefCell`. - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] Guard(Ref<'d, Dynamic>), /// A read guard to a shared `RwLock`. - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] #[cfg(feature = "sync")] Guard(RwLockReadGuard<'d, Dynamic>), } @@ -193,7 +193,7 @@ impl<'d, T: Variant + Clone> Deref for DynamicReadLock<'d, T> { match &self.0 { DynamicReadLockInner::Reference(reference) => *reference, // Unwrapping is safe because all checking is already done in its constructor - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] DynamicReadLockInner::Guard(guard) => guard.downcast_ref().unwrap(), } } @@ -212,11 +212,11 @@ enum DynamicWriteLockInner<'d, T: Variant + Clone> { /// A simple mutable reference to a non-shared value. Reference(&'d mut T), /// A write guard to a shared `RefCell`. - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] Guard(RefMut<'d, Dynamic>), /// A write guard to a shared `RwLock`. - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] #[cfg(feature = "sync")] Guard(RwLockWriteGuard<'d, Dynamic>), } @@ -229,7 +229,7 @@ impl<'d, T: Variant + Clone> Deref for DynamicWriteLock<'d, T> { match &self.0 { DynamicWriteLockInner::Reference(reference) => *reference, // Unwrapping is safe because all checking is already done in its constructor - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] DynamicWriteLockInner::Guard(guard) => guard.downcast_ref().unwrap(), } } @@ -241,7 +241,7 @@ impl<'d, T: Variant + Clone> DerefMut for DynamicWriteLock<'d, T> { match &mut self.0 { DynamicWriteLockInner::Reference(reference) => *reference, // Unwrapping is safe because all checking is already done in its constructor - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] DynamicWriteLockInner::Guard(guard) => guard.downcast_mut().unwrap(), } } @@ -261,7 +261,7 @@ impl Dynamic { /// instead of one of the supported system primitive types? pub fn is_shared(&self) -> bool { match self.0 { - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] Union::Shared(_) => true, _ => false, } @@ -302,10 +302,10 @@ impl Dynamic { Union::Map(_) => TypeId::of::(), Union::FnPtr(_) => TypeId::of::(), Union::Variant(value) => (***value).type_id(), - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] Union::Shared(cell) => (*cell.borrow()).type_id(), - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] #[cfg(feature = "sync")] Union::Shared(cell) => (*cell.read().unwrap()).type_id(), } @@ -335,10 +335,10 @@ impl Dynamic { #[cfg(not(feature = "no_std"))] Union::Variant(value) if value.is::() => "timestamp", Union::Variant(value) => (***value).type_name(), - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] Union::Shared(cell) => (*cell.borrow()).type_name(), - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] #[cfg(feature = "sync")] Union::Shared(cell) => (*cell.read().unwrap()).type_name(), } @@ -396,7 +396,7 @@ impl fmt::Display for Dynamic { #[cfg(not(feature = "no_std"))] Union::Variant(value) if value.is::() => write!(f, ""), Union::Variant(value) => write!(f, "{}", (*value).type_name()), - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] Union::Shared(_) => f.write_str(""), } } @@ -424,7 +424,7 @@ impl fmt::Debug for Dynamic { #[cfg(not(feature = "no_std"))] Union::Variant(value) if value.is::() => write!(f, ""), Union::Variant(value) => write!(f, "{}", (*value).type_name()), - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] Union::Shared(_) => f.write_str(""), } } @@ -446,7 +446,7 @@ impl Clone for Dynamic { Union::Map(ref value) => Self(Union::Map(value.clone())), Union::FnPtr(ref value) => Self(Union::FnPtr(value.clone())), Union::Variant(ref value) => (***value).clone_into_dynamic(), - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell) => Self(Union::Shared(cell.clone())), } } @@ -571,9 +571,9 @@ impl Dynamic { /// /// # Panics /// - /// Panics under the `no_shared` feature. + /// Panics under the `no_closure` feature. pub fn into_shared(self) -> Self { - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] return match self.0 { Union::Shared(..) => self, #[cfg(not(feature = "sync"))] @@ -582,7 +582,7 @@ impl Dynamic { _ => Self(Union::Shared(Arc::new(RwLock::new(self)))), }; - #[cfg(feature = "no_shared")] + #[cfg(feature = "no_closure")] unimplemented!() } @@ -614,11 +614,11 @@ impl Dynamic { let type_id = TypeId::of::(); match self.0 { - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] Union::Shared(cell) => return cell.borrow().clone().try_cast(), - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] #[cfg(feature = "sync")] Union::Shared(cell) => return cell.read().unwrap().clone().try_cast(), _ => (), @@ -703,7 +703,7 @@ impl Dynamic { match self.0 { Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(), - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] Union::Shared(_) => unreachable!(), _ => None, } @@ -748,17 +748,17 @@ impl Dynamic { /// /// Returns `None` if the cast fails. #[inline(always)] - pub fn clone_inner_data(&self) -> Option { + pub fn clone_inner_data(self) -> Option { match self.0 { - #[cfg(not(feature = "no_shared"))] - Union::Shared(ref cell) => { + #[cfg(not(feature = "no_closure"))] + Union::Shared(cell) => { #[cfg(not(feature = "sync"))] return Some(cell.borrow().downcast_ref::().unwrap().clone()); #[cfg(feature = "sync")] return Some(cell.read().unwrap().downcast_ref::().unwrap().clone()); } - _ => self.downcast_ref().cloned(), + _ => self.try_cast(), } } @@ -772,7 +772,7 @@ impl Dynamic { #[inline(always)] pub fn is_locked(&self) -> bool { match self.0 { - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] Union::Shared(ref _cell) => { #[cfg(not(feature = "sync"))] return _cell.try_borrow().is_err(); @@ -788,22 +788,33 @@ impl Dynamic { /// Casting to `Dynamic` just returns a reference to it. /// /// Returns `None` if the cast fails. + /// + /// # Panics and Deadlocks When Value is Shared + /// + /// Under the `sync` feature, this call may deadlock. + /// Otherwise, this call panics if the data is currently borrowed for write. #[inline(always)] pub fn read_lock(&self) -> Option> { match self.0 { - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell) => { #[cfg(not(feature = "sync"))] - return Some(DynamicReadLock(DynamicReadLockInner::Guard(cell.borrow()))); + let data = cell.borrow(); #[cfg(feature = "sync")] - return Some(DynamicReadLock(DynamicReadLockInner::Guard( - cell.read().unwrap(), - ))); + let data = cell.read().unwrap(); + + let type_id = (*data).type_id(); + + if type_id != TypeId::of::() && TypeId::of::() != TypeId::of::() { + None + } else { + Some(DynamicReadLock(DynamicReadLockInner::Guard(data))) + } } _ => self .downcast_ref() - .map(|reference| DynamicReadLock(DynamicReadLockInner::Reference(reference))), + .map(|r| DynamicReadLock(DynamicReadLockInner::Reference(r))), } } @@ -811,24 +822,33 @@ impl Dynamic { /// Casting to `Dynamic` just returns a mutable reference to it. /// /// Returns `None` if the cast fails. + /// + /// # Panics and Deadlocks When Value is Shared + /// + /// Under the `sync` feature, this call may deadlock. + /// Otherwise, this call panics if the data is currently borrowed for write. #[inline(always)] pub fn write_lock(&mut self) -> Option> { match self.0 { - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell) => { #[cfg(not(feature = "sync"))] - return Some(DynamicWriteLock(DynamicWriteLockInner::Guard( - cell.borrow_mut(), - ))); + let data = cell.borrow_mut(); #[cfg(feature = "sync")] - return Some(DynamicWriteLock(DynamicWriteLockInner::Guard( - cell.write().unwrap(), - ))); + let data = cell.write().unwrap(); + + let type_id = (*data).type_id(); + + if type_id != TypeId::of::() && TypeId::of::() != TypeId::of::() { + None + } else { + Some(DynamicWriteLock(DynamicWriteLockInner::Guard(data))) + } } _ => self .downcast_mut() - .map(|reference| DynamicWriteLock(DynamicWriteLockInner::Reference(reference))), + .map(|r| DynamicWriteLock(DynamicWriteLockInner::Reference(r))), } } @@ -836,6 +856,10 @@ impl Dynamic { /// Casting to `Dynamic` just returns a reference to it. /// /// Returns `None` if the cast fails. + /// + /// # Panics + /// + /// Panics if the value is shared. #[inline(always)] fn downcast_ref(&self) -> Option<&T> { let type_id = TypeId::of::(); @@ -909,7 +933,7 @@ impl Dynamic { match &self.0 { Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::(), - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] Union::Shared(_) => unreachable!(), _ => None, } @@ -919,6 +943,10 @@ impl Dynamic { /// Casting to `Dynamic` just returns a mutable reference to it. /// /// Returns `None` if the cast fails. + /// + /// # Panics + /// + /// Panics if the value is shared. #[inline(always)] fn downcast_mut(&mut self) -> Option<&mut T> { let type_id = TypeId::of::(); @@ -986,7 +1014,7 @@ impl Dynamic { match &mut self.0 { Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::(), - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] Union::Shared(_) => unreachable!(), _ => None, } @@ -997,10 +1025,8 @@ impl Dynamic { pub fn as_int(&self) -> Result { match self.0 { Union::Int(n) => Ok(n), - #[cfg(not(feature = "no_shared"))] - Union::Shared(_) => self - .clone_inner_data::() - .ok_or_else(|| self.type_name()), + #[cfg(not(feature = "no_closure"))] + Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -1011,10 +1037,8 @@ impl Dynamic { pub fn as_float(&self) -> Result { match self.0 { Union::Float(n) => Ok(n), - #[cfg(not(feature = "no_shared"))] - Union::Shared(_) => self - .clone_inner_data::() - .ok_or_else(|| self.type_name()), + #[cfg(not(feature = "no_closure"))] + Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -1024,10 +1048,8 @@ impl Dynamic { pub fn as_bool(&self) -> Result { match self.0 { Union::Bool(b) => Ok(b), - #[cfg(not(feature = "no_shared"))] - Union::Shared(_) => self - .clone_inner_data::() - .ok_or_else(|| self.type_name()), + #[cfg(not(feature = "no_closure"))] + Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -1037,10 +1059,8 @@ impl Dynamic { pub fn as_char(&self) -> Result { match self.0 { Union::Char(n) => Ok(n), - #[cfg(not(feature = "no_shared"))] - Union::Shared(_) => self - .clone_inner_data::() - .ok_or_else(|| self.type_name()), + #[cfg(not(feature = "no_closure"))] + Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -1070,7 +1090,7 @@ impl Dynamic { match self.0 { Union::Str(s) => Ok(s), Union::FnPtr(f) => Ok(f.take_data().0), - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] Union::Shared(cell) => { #[cfg(not(feature = "sync"))] { diff --git a/src/api.rs b/src/api.rs index be69f53d..43aaf6d5 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1284,7 +1284,7 @@ impl Engine { let args = args.as_mut(); // Check for data race. - if cfg!(not(feature = "no_shared")) { + if cfg!(not(feature = "no_closure")) { ensure_no_data_race(name, args, false)?; } diff --git a/src/engine.rs b/src/engine.rs index b0bd5e8b..be915025 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -31,7 +31,7 @@ use crate::module::resolvers; #[cfg(any(not(feature = "no_object"), not(feature = "no_module")))] use crate::utils::ImmutableString; -#[cfg(not(feature = "no_shared"))] +#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_object"))] use crate::any::DynamicWriteLock; @@ -49,6 +49,9 @@ use crate::stdlib::{ #[cfg(not(feature = "no_index"))] use crate::stdlib::any::TypeId; +#[cfg(not(feature = "no_closure"))] +use crate::stdlib::mem; + /// Variable-sized array of `Dynamic` values. /// /// Not available under the `no_index` feature. @@ -96,8 +99,6 @@ pub const KEYWORD_EVAL: &str = "eval"; pub const KEYWORD_FN_PTR: &str = "Fn"; pub const KEYWORD_FN_PTR_CALL: &str = "call"; pub const KEYWORD_FN_PTR_CURRY: &str = "curry"; -pub const KEYWORD_SHARED: &str = "shared"; -pub const KEYWORD_TAKE: &str = "take"; pub const KEYWORD_IS_SHARED: &str = "is_shared"; pub const KEYWORD_THIS: &str = "this"; pub const FN_TO_STRING: &str = "to_string"; @@ -132,7 +133,7 @@ pub enum Target<'a> { Ref(&'a mut Dynamic), /// The target is a mutable reference to a Shared `Dynamic` value. /// It holds both the access guard and the original shared value. - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_object"))] LockGuard((DynamicWriteLock<'a, Dynamic>, Dynamic)), /// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects). @@ -149,7 +150,7 @@ impl Target<'_> { pub fn is_ref(&self) -> bool { match self { Self::Ref(_) => true, - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_object"))] Self::LockGuard(_) => true, Self::Value(_) => false, @@ -161,7 +162,7 @@ impl Target<'_> { pub fn is_value(&self) -> bool { match self { Self::Ref(_) => false, - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_object"))] Self::LockGuard(_) => false, Self::Value(_) => true, @@ -173,7 +174,7 @@ impl Target<'_> { pub fn is_shared(&self) -> bool { match self { Self::Ref(r) => r.is_shared(), - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_object"))] Self::LockGuard(_) => true, Self::Value(r) => r.is_shared(), @@ -186,7 +187,7 @@ impl Target<'_> { pub fn is(&self) -> bool { match self { Target::Ref(r) => r.is::(), - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_object"))] Target::LockGuard((r, _)) => r.is::(), Target::Value(r) => r.is::(), @@ -198,7 +199,7 @@ impl Target<'_> { pub fn clone_into_dynamic(self) -> Dynamic { match self { Self::Ref(r) => r.clone(), // Referenced value is cloned - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_object"))] Self::LockGuard((_, orig)) => orig, // Original value is simply taken Self::Value(v) => v, // Owned value is simply taken @@ -210,7 +211,7 @@ impl Target<'_> { pub fn as_mut(&mut self) -> &mut Dynamic { match self { Self::Ref(r) => *r, - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_object"))] Self::LockGuard((r, _)) => r.deref_mut(), Self::Value(ref mut r) => r, @@ -223,7 +224,7 @@ impl Target<'_> { pub fn set_value(&mut self, new_val: Dynamic) -> Result<(), Box> { match self { Self::Ref(r) => **r = new_val, - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_object"))] Self::LockGuard((r, _)) => **r = new_val, Self::Value(_) => { @@ -260,7 +261,7 @@ impl Target<'_> { #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] impl<'a> From<&'a mut Dynamic> for Target<'a> { fn from(value: &'a mut Dynamic) -> Self { - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_object"))] if value.is_shared() { // Cloning is cheap for a shared value @@ -632,7 +633,7 @@ pub fn search_scope_only<'s, 'a>( // Check for data race - probably not necessary because the only place it should conflict is in a method call // when the object variable is also used as a parameter. - // if cfg!(not(feature = "no_shared")) && val.is_locked() { + // if cfg!(not(feature = "no_closure")) && val.is_locked() { // return Err(Box::new(EvalAltResult::ErrorDataRace(name.into(), *pos))); // } @@ -1351,12 +1352,9 @@ impl Engine { )), // Normal assignment ScopeEntryType::Normal if op.is_empty() => { - if cfg!(not(feature = "no_shared")) && lhs_ptr.is_shared() { - *lhs_ptr.write_lock::().unwrap() = if rhs_val.is_shared() { - rhs_val.clone_inner_data().unwrap() - } else { - rhs_val - }; + let rhs_val = rhs_val.clone_inner_data().unwrap(); + if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { + *lhs_ptr.write_lock::().unwrap() = rhs_val; } else { *lhs_ptr = rhs_val; } @@ -1377,7 +1375,7 @@ impl Engine { .get_fn(hash_fn, false) .or_else(|| self.packages.get_fn(hash_fn, false)) { - if cfg!(not(feature = "no_shared")) && lhs_ptr.is_shared() { + if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { let mut lock_guard = lhs_ptr.write_lock::().unwrap(); let lhs_ptr_inner = lock_guard.deref_mut(); @@ -1401,12 +1399,9 @@ impl Engine { ) .map_err(|err| err.new_position(*op_pos))?; - if cfg!(not(feature = "no_shared")) && lhs_ptr.is_shared() { - *lhs_ptr.write_lock::().unwrap() = if value.is_shared() { - value.clone_inner_data().unwrap() - } else { - value - }; + let value = value.clone_inner_data().unwrap(); + if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { + *lhs_ptr.write_lock::().unwrap() = value; } else { *lhs_ptr = value; } @@ -1845,6 +1840,25 @@ impl Engine { } Ok(Default::default()) } + + // Share statement + #[cfg(not(feature = "no_closure"))] + Stmt::Share(x) => { + let (var_name, _) = x.as_ref(); + + match scope.get_index(var_name) { + Some((index, ScopeEntryType::Normal)) => { + let (val, _) = scope.get_mut(index); + + if !val.is_shared() { + // Replace the variable with a shared value. + *val = mem::take(val).into_shared(); + } + } + _ => (), + } + Ok(Default::default()) + } }; self.check_data_size(result) diff --git a/src/error.rs b/src/error.rs index 259d6916..1d5ea7f8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -93,7 +93,7 @@ pub enum ParseErrorType { MalformedInExpr(String), /// A capturing has syntax error. Wrapped value is the error description (if any). /// - /// Never appears under the `no_capture` feature. + /// Never appears under the `no_closure` feature. MalformedCapture(String), /// A map definition has duplicated property names. Wrapped value is the property name. /// diff --git a/src/fn_call.rs b/src/fn_call.rs index 6980caca..d56487e2 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -5,7 +5,7 @@ use crate::calc_fn_hash; use crate::engine::{ search_imports, search_namespace, search_scope_only, Engine, Imports, State, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_SHARED, - KEYWORD_PRINT, KEYWORD_SHARED, KEYWORD_TAKE, KEYWORD_TYPE_OF, + KEYWORD_PRINT, KEYWORD_TYPE_OF, }; use crate::error::ParseErrorType; use crate::fn_native::{FnCallArgs, FnPtr}; @@ -33,7 +33,7 @@ use crate::engine::{FN_IDX_GET, FN_IDX_SET}; #[cfg(not(feature = "no_object"))] use crate::engine::{Map, Target, FN_GET, FN_SET}; -#[cfg(not(feature = "no_capture"))] +#[cfg(not(feature = "no_closure"))] use crate::scope::Entry as ScopeEntry; use crate::stdlib::{ @@ -47,7 +47,7 @@ use crate::stdlib::{ vec::Vec, }; -#[cfg(not(feature = "no_capture"))] +#[cfg(not(feature = "no_closure"))] use crate::stdlib::{collections::HashSet, string::String}; /// Extract the property name from a getter function name. @@ -139,7 +139,7 @@ impl Drop for ArgBackup<'_> { } // Add captured variables into scope -#[cfg(not(feature = "no_capture"))] +#[cfg(not(feature = "no_closure"))] fn add_captured_variables_into_scope<'s>( externals: &HashSet, captured: Scope<'s>, @@ -166,7 +166,7 @@ pub fn ensure_no_data_race( args: &FnCallArgs, is_ref: bool, ) -> Result<(), Box> { - if cfg!(not(feature = "no_shared")) { + if cfg!(not(feature = "no_closure")) { let skip = if is_ref { 1 } else { 0 }; if let Some((n, _)) = args @@ -456,7 +456,7 @@ impl Engine { level: usize, ) -> Result<(Dynamic, bool), Box> { // Check for data race. - if cfg!(not(feature = "no_shared")) { + if cfg!(not(feature = "no_closure")) { ensure_no_data_race(fn_name, args, is_ref)?; } @@ -505,7 +505,7 @@ impl Engine { let mods = &mut Imports::new(); // Add captured variables into scope - #[cfg(not(feature = "no_capture"))] + #[cfg(not(feature = "no_closure"))] if let Some(captured) = _capture { add_captured_variables_into_scope(&func.externals, captured, scope); } @@ -690,18 +690,12 @@ impl Engine { .into(), false, )) - } else if cfg!(not(feature = "no_shared")) + } else if cfg!(not(feature = "no_closure")) && _fn_name == KEYWORD_IS_SHARED && idx.is_empty() { // take call Ok((target.is_shared().into(), false)) - } else if cfg!(not(feature = "no_shared")) && _fn_name == KEYWORD_SHARED && idx.is_empty() { - // take call - Ok((obj.clone().into_shared(), false)) - } else if cfg!(not(feature = "no_shared")) && _fn_name == KEYWORD_TAKE && idx.is_empty() { - // take call - Ok((obj.clone_inner_data::().unwrap(), false)) } else { #[cfg(not(feature = "no_object"))] let redirected; @@ -814,29 +808,13 @@ impl Engine { } // Handle is_shared() - if cfg!(not(feature = "no_shared")) && name == KEYWORD_IS_SHARED && args_expr.len() == 1 { + if cfg!(not(feature = "no_closure")) && name == KEYWORD_IS_SHARED && args_expr.len() == 1 { let expr = args_expr.get(0).unwrap(); let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; return Ok(value.is_shared().into()); } - // Handle shared() - if cfg!(not(feature = "no_shared")) && name == KEYWORD_SHARED && args_expr.len() == 1 { - let expr = args_expr.get(0).unwrap(); - let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; - - return Ok(value.into_shared()); - } - - // Handle take() - if cfg!(not(feature = "no_shared")) && name == KEYWORD_TAKE && args_expr.len() == 1 { - let expr = args_expr.get(0).unwrap(); - let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; - - return Ok(value.clone_inner_data::().unwrap()); - } - // Handle call() - Redirect function call let redirected; let mut args_expr = args_expr.as_ref(); @@ -896,7 +874,7 @@ impl Engine { let mut arg_values: StaticVec<_>; let mut args: StaticVec<_>; let mut is_ref = false; - let capture = if cfg!(not(feature = "no_capture")) && capture && !scope.is_empty() { + let capture = if cfg!(not(feature = "no_closure")) && capture && !scope.is_empty() { Some(scope.flatten_clone()) } else { None @@ -924,7 +902,7 @@ impl Engine { // Turn it into a method call only if the object is not shared args = if target.is_shared() { - arg_values.insert(0, target.clone_inner_data().unwrap()); + arg_values.insert(0, target.clone().clone_inner_data().unwrap()); arg_values.iter_mut().collect() } else { is_ref = true; @@ -1041,7 +1019,7 @@ impl Engine { let mods = &mut Imports::new(); // Add captured variables into scope - #[cfg(not(feature = "no_capture"))] + #[cfg(not(feature = "no_closure"))] if _capture && !scope.is_empty() { add_captured_variables_into_scope( &func.externals, diff --git a/src/fn_native.rs b/src/fn_native.rs index f8bb0bf1..a972793b 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -22,10 +22,10 @@ use crate::stdlib::rc::Rc; #[cfg(feature = "sync")] use crate::stdlib::sync::Arc; -#[cfg(not(feature = "no_shared"))] +#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] use crate::stdlib::cell::RefCell; -#[cfg(not(feature = "no_shared"))] +#[cfg(not(feature = "no_closure"))] #[cfg(feature = "sync")] use crate::stdlib::sync::RwLock; @@ -51,11 +51,11 @@ pub type Shared = Rc; pub type Shared = Arc; /// Mutable reference-counted container (read-write lock) -#[cfg(not(feature = "no_shared"))] +#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] pub type SharedMut = Shared>; /// Mutable reference-counted container (read-write lock) -#[cfg(not(feature = "no_shared"))] +#[cfg(not(feature = "no_closure"))] #[cfg(feature = "sync")] pub type SharedMut = Shared>; diff --git a/src/optimize.rs b/src/optimize.rs index 9072a550..7503c592 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -765,7 +765,7 @@ pub fn optimize_into_ast( access: fn_def.access, body: Default::default(), params: fn_def.params.clone(), - #[cfg(not(feature = "no_capture"))] + #[cfg(not(feature = "no_closure"))] externals: fn_def.externals.clone(), pos: fn_def.pos, } diff --git a/src/parser.rs b/src/parser.rs index 08869146..7f29bbcd 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -39,7 +39,7 @@ use crate::stdlib::{ #[cfg(not(feature = "no_function"))] use crate::stdlib::collections::hash_map::DefaultHasher; -#[cfg(not(feature = "no_capture"))] +#[cfg(not(feature = "no_closure"))] use crate::stdlib::collections::HashSet; #[cfg(feature = "no_std")] @@ -366,7 +366,7 @@ pub struct ScriptFnDef { /// Names of function parameters. pub params: StaticVec, /// Access to external variables. - #[cfg(not(feature = "no_capture"))] + #[cfg(not(feature = "no_closure"))] pub externals: HashSet, /// Function body. pub body: Stmt, @@ -414,13 +414,13 @@ struct ParseState<'e> { /// Encapsulates a local stack with variable names to simulate an actual runtime scope. stack: Vec<(String, ScopeEntryType)>, /// Tracks a list of external variables (variables that are not explicitly declared in the scope). - #[cfg(not(feature = "no_capture"))] + #[cfg(not(feature = "no_closure"))] externals: HashMap, - /// An indicator that prevents variables capturing into externals one time. - /// If set to true the next call of `access_var` will not capture the variable. + /// An indicator that disables variable capturing into externals one single time. + /// If set to false the next call to `access_var` will not capture the variable. /// All consequent calls to `access_var` will not be affected - #[cfg(not(feature = "no_capture"))] - capture: bool, + #[cfg(not(feature = "no_closure"))] + allow_capture: bool, /// Encapsulates a local stack with variable names to simulate an actual runtime scope. modules: Vec, /// Maximum levels of expression nesting. @@ -444,10 +444,10 @@ impl<'e> ParseState<'e> { max_expr_depth, #[cfg(not(feature = "unchecked"))] max_function_expr_depth, - #[cfg(not(feature = "no_capture"))] + #[cfg(not(feature = "no_closure"))] externals: Default::default(), - #[cfg(not(feature = "no_capture"))] - capture: true, + #[cfg(not(feature = "no_closure"))] + allow_capture: true, stack: Default::default(), modules: Default::default(), } @@ -469,13 +469,13 @@ impl<'e> ParseState<'e> { .find(|(_, (n, _))| *n == name) .and_then(|(i, _)| NonZeroUsize::new(i + 1)); - #[cfg(not(feature = "no_capture"))] - if self.capture { + #[cfg(not(feature = "no_closure"))] + if self.allow_capture { if index.is_none() && !self.externals.contains_key(name) { self.externals.insert(name.to_string(), _pos); } } else { - self.capture = true + self.allow_capture = true } index @@ -579,6 +579,9 @@ pub enum Stmt { Position, )>, ), + /// Convert a variable to shared. + #[cfg(not(feature = "no_closure"))] + Share(Box<(String, Position)>), } impl Default for Stmt { @@ -606,6 +609,9 @@ impl Stmt { Stmt::Import(x) => x.2, #[cfg(not(feature = "no_module"))] Stmt::Export(x) => x.1, + + #[cfg(not(feature = "no_closure"))] + Stmt::Share(x) => x.1, } } @@ -629,6 +635,9 @@ impl Stmt { Stmt::Import(x) => x.2 = new_pos, #[cfg(not(feature = "no_module"))] Stmt::Export(x) => x.1 = new_pos, + + #[cfg(not(feature = "no_closure"))] + Stmt::Share(x) => x.1 = new_pos, } self @@ -655,6 +664,9 @@ impl Stmt { #[cfg(not(feature = "no_module"))] Stmt::Import(_) | Stmt::Export(_) => false, + + #[cfg(not(feature = "no_closure"))] + Stmt::Share(_) => false, } } @@ -678,6 +690,9 @@ impl Stmt { Stmt::Import(_) => false, #[cfg(not(feature = "no_module"))] Stmt::Export(_) => false, + + #[cfg(not(feature = "no_closure"))] + Stmt::Share(_) => false, } } } @@ -1671,7 +1686,7 @@ fn parse_primary( root_expr = match (root_expr, token) { // Function call - #[cfg(not(feature = "no_capture"))] + #[cfg(not(feature = "no_closure"))] (Expr::Variable(x), Token::Bang) => { if !match_token(input, Token::LeftParen)? { return Err(PERR::MissingToken( @@ -1855,7 +1870,7 @@ fn parse_unary( let (expr, func) = parse_anon_fn(input, &mut new_state, lib, settings)?; - #[cfg(not(feature = "no_capture"))] + #[cfg(not(feature = "no_closure"))] new_state.externals.iter().for_each(|(closure, pos)| { state.access_var(closure, *pos); }); @@ -2216,9 +2231,9 @@ fn parse_binary_op( if cfg!(not(feature = "no_object")) && op_token == Token::Period { if let (Token::Identifier(_), _) = input.peek().unwrap() { // prevents capturing of the object properties as vars: xxx. - #[cfg(not(feature = "no_capture"))] + #[cfg(not(feature = "no_closure"))] { - state.capture = false; + state.allow_capture = false; } } } @@ -3095,7 +3110,7 @@ fn parse_fn( let params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect(); - #[cfg(not(feature = "no_capture"))] + #[cfg(not(feature = "no_closure"))] let externals = state .externals .iter() @@ -3108,7 +3123,7 @@ fn parse_fn( name: name.into(), access, params, - #[cfg(not(feature = "no_capture"))] + #[cfg(not(feature = "no_closure"))] externals, body, pos: settings.pos, @@ -3128,6 +3143,17 @@ fn make_curry_from_externals( let num_externals = externals.len(); let mut args: StaticVec<_> = Default::default(); + #[cfg(not(feature = "no_closure"))] + externals.iter().for_each(|(var_name, pos)| { + args.push(Expr::Variable(Box::new(( + (var_name.into(), *pos), + None, + 0, + None, + )))); + }); + + #[cfg(feature = "no_closure")] externals.into_iter().for_each(|(var_name, pos)| { args.push(Expr::Variable(Box::new(((var_name, pos), None, 0, None)))); }); @@ -3142,7 +3168,23 @@ fn make_curry_from_externals( None, ))); - Expr::Dot(Box::new((fn_expr, fn_call, pos))) + let expr = Expr::Dot(Box::new((fn_expr, fn_call, pos))); + + // If there are captured variables, convert the entire expression into a statement block, + // then insert the relevant `Share` statements. + #[cfg(not(feature = "no_closure"))] + { + // Statement block + 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(Box::new(expr))); + Expr::Stmt(Box::new((Stmt::Block(Box::new((statements, pos))), pos))) + } + + #[cfg(feature = "no_closure")] + return expr; } /// Parse an anonymous function definition. @@ -3215,7 +3257,7 @@ fn parse_anon_fn( // External variables may need to be processed in a consistent order, // so extract them into a list. let externals: StaticVec<_> = { - #[cfg(not(feature = "no_capture"))] + #[cfg(not(feature = "no_closure"))] { state .externals @@ -3223,11 +3265,11 @@ fn parse_anon_fn( .map(|(k, &v)| (k.clone(), v)) .collect() } - #[cfg(feature = "no_capture")] + #[cfg(feature = "no_closure")] Default::default() }; - let params: StaticVec<_> = if cfg!(not(feature = "no_capture")) { + let params: StaticVec<_> = if cfg!(not(feature = "no_closure")) { externals .iter() .map(|(k, _)| k) @@ -3257,7 +3299,7 @@ fn parse_anon_fn( name: fn_name.clone(), access: FnAccess::Public, params, - #[cfg(not(feature = "no_capture"))] + #[cfg(not(feature = "no_closure"))] externals: Default::default(), body, pos: settings.pos, @@ -3265,7 +3307,7 @@ fn parse_anon_fn( let expr = Expr::FnPointer(Box::new((fn_name, settings.pos))); - let expr = if cfg!(not(feature = "no_capture")) { + let expr = if cfg!(not(feature = "no_closure")) { make_curry_from_externals(expr, externals, settings.pos) } else { expr diff --git a/src/scope.rs b/src/scope.rs index 37f71e99..836108c7 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -158,32 +158,6 @@ impl<'a> Scope<'a> { self.push_dynamic_value(name, EntryType::Normal, Dynamic::from(value), false) } - /// Add (push) a new shared entry to the Scope. - /// - /// # Examples - /// - /// ``` - /// use rhai::Scope; - /// - /// let mut my_scope = Scope::new(); - /// - /// my_scope.push_shared("x", 42_i64); - /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); - /// ``` - #[cfg(not(feature = "no_shared"))] - pub fn push_shared>, T: Variant + Clone>( - &mut self, - name: K, - value: T, - ) -> &mut Self { - self.push_dynamic_value( - name, - EntryType::Normal, - Dynamic::from(value).into_shared(), - false, - ) - } - /// Add (push) a new `Dynamic` entry to the Scope. /// /// # Examples @@ -226,34 +200,6 @@ impl<'a> Scope<'a> { self.push_dynamic_value(name, EntryType::Constant, Dynamic::from(value), true) } - /// Add (push) a new shared constant to the Scope. - /// - /// Shared constants are immutable and cannot be assigned to, but their shared values can change. - /// - /// # Examples - /// - /// ``` - /// use rhai::Scope; - /// - /// let mut my_scope = Scope::new(); - /// - /// my_scope.push_constant_shared("x", 42_i64); - /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); - /// ``` - #[cfg(not(feature = "no_shared"))] - pub fn push_constant_shared>, T: Variant + Clone>( - &mut self, - name: K, - value: T, - ) -> &mut Self { - self.push_dynamic_value( - name, - EntryType::Constant, - Dynamic::from(value).into_shared(), - true, - ) - } - /// Add (push) a new constant with a `Dynamic` value to the Scope. /// /// Constants are immutable and cannot be assigned to. Their values never change. @@ -394,7 +340,7 @@ impl<'a> Scope<'a> { /// ``` pub fn get_value(&self, name: &str) -> Option { self.get_entry(name) - .and_then(|Entry { value, .. }| value.clone_inner_data::()) + .and_then(|Entry { value, .. }| value.clone().clone_inner_data::()) } /// Update the value of the named entry. @@ -485,13 +431,20 @@ impl<'a> Scope<'a> { /// /// let (name, value) = iter.next().unwrap(); /// assert_eq!(name, "x"); - /// assert_eq!(value.clone().cast::(), 42); + /// assert_eq!(value.cast::(), 42); /// /// let (name, value) = iter.next().unwrap(); /// assert_eq!(name, "foo"); - /// assert_eq!(value.clone().cast::(), "hello"); + /// assert_eq!(value.cast::(), "hello"); /// ``` - pub fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator { + self.iter_raw() + .map(|(name, value)| (name, value.clone().clone_inner_data().unwrap())) + } + + /// Get an iterator to entries in the Scope. + /// Shared values are not expanded. + pub fn iter_raw(&self) -> impl Iterator { self.0 .iter() .map(|Entry { name, value, .. }| (name.as_ref(), value)) diff --git a/src/token.rs b/src/token.rs index 920fb33a..4a4d5fc1 100644 --- a/src/token.rs +++ b/src/token.rs @@ -2,7 +2,7 @@ use crate::engine::{ Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, - KEYWORD_IS_SHARED, KEYWORD_PRINT, KEYWORD_SHARED, KEYWORD_TAKE, KEYWORD_THIS, KEYWORD_TYPE_OF, + KEYWORD_IS_SHARED, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF, }; use crate::error::LexError; @@ -501,14 +501,15 @@ impl Token { "import" | "export" | "as" => Reserved(syntax.into()), "===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "public" - | "new" | "use" | "module" | "package" | "var" | "static" | "with" | "do" | "each" - | "then" | "goto" | "exit" | "switch" | "match" | "case" | "try" | "catch" - | "default" | "void" | "null" | "nil" | "spawn" | "go" | "sync" | "async" | "await" - | "yield" => Reserved(syntax.into()), + | "new" | "use" | "module" | "package" | "var" | "static" | "shared" | "with" + | "do" | "each" | "then" | "goto" | "exit" | "switch" | "match" | "case" | "try" + | "catch" | "default" | "void" | "null" | "nil" | "spawn" | "go" | "sync" | "async" + | "await" | "yield" => Reserved(syntax.into()), KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR - | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_SHARED | KEYWORD_SHARED - | KEYWORD_TAKE | KEYWORD_THIS => Reserved(syntax.into()), + | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_SHARED | KEYWORD_THIS => { + Reserved(syntax.into()) + } _ => return None, }) @@ -1432,8 +1433,8 @@ fn get_identifier( #[inline(always)] pub fn is_keyword_function(name: &str) -> bool { match name { - #[cfg(not(feature = "no_shared"))] - KEYWORD_SHARED | KEYWORD_TAKE | KEYWORD_IS_SHARED => true, + #[cfg(not(feature = "no_closure"))] + KEYWORD_IS_SHARED => true, KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY => true, _ => false, diff --git a/tests/closures.rs b/tests/closures.rs index c5282870..f0a91ac6 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -35,7 +35,8 @@ fn test_fn_ptr_curry_call() -> Result<(), Box> { } #[test] -#[cfg(all(not(feature = "no_capture"), not(feature = "no_object")))] +#[cfg(not(feature = "no_closure"))] +#[cfg(not(feature = "no_object"))] fn test_closures() -> Result<(), Box> { let engine = Engine::new(); @@ -56,5 +57,61 @@ fn test_closures() -> Result<(), Box> { 42 ); + assert_eq!( + engine.eval::( + r#" + let a = 41; + let foo = |x| { a += x }; + foo.call(1); + a + "# + )?, + 42 + ); + + assert!(engine.eval::( + r#" + let a = 41; + let foo = |x| { a += x }; + a.is_shared() + "# + )?); + + Ok(()) +} + +#[test] +#[cfg(not(feature = "no_closure"))] +#[cfg(not(feature = "no_object"))] +fn test_closures_data_race() -> Result<(), Box> { + let engine = Engine::new(); + + assert_eq!( + engine.eval::( + r#" + let a = 1; + let b = 40; + let foo = |x| { this += a + x }; + b.call(foo, 1); + b + "# + )?, + 42 + ); + + assert!(matches!( + *engine + .eval::( + r#" + let a = 20; + let foo = |x| { this += a + x }; + a.call(foo, 1); + a + "# + ) + .expect_err("should error"), + EvalAltResult::ErrorDataRace(_, _) + )); + Ok(()) } diff --git a/tests/functions.rs b/tests/functions.rs index 9336cb6d..58ef8d99 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -122,7 +122,7 @@ fn test_function_pointers() -> Result<(), Box> { } #[test] -#[cfg(not(feature = "no_capture"))] +#[cfg(not(feature = "no_closure"))] fn test_function_captures() -> Result<(), Box> { let engine = Engine::new(); diff --git a/tests/shared.rs b/tests/shared.rs deleted file mode 100644 index b2238fbf..00000000 --- a/tests/shared.rs +++ /dev/null @@ -1,324 +0,0 @@ -#![cfg(not(feature = "no_shared"))] - -use rhai::{Array, Dynamic, Engine, EvalAltResult, FnPtr, Module, RegisterFn, INT}; -use std::any::TypeId; - -#[test] -fn test_shared() -> Result<(), Box> { - let mut engine = Engine::new(); - - assert_eq!(engine.eval::("shared(42)")?, 42); - - assert_eq!(engine.eval::("shared(true)")?, true); - - assert_eq!( - engine.eval::("let x = shared(true); type_of(x)")?, - "bool" - ); - - assert_eq!( - engine.eval::("let x = shared(true); x = (); type_of(x)")?, - "()" - ); - - #[cfg(not(feature = "no_float"))] - assert_eq!(engine.eval::("shared(4.2)")?, 4.2); - - assert_eq!(engine.eval::(r#"shared("test")"#)?, "test"); - - assert_eq!(engine.eval::(r#"shared("test")"#)?, "test"); - - assert_eq!(engine.eval::("shared('x')")?, 'x'); - - assert!(engine.eval::("is_shared(shared(42))")?); - - #[cfg(not(feature = "no_object"))] - assert!(engine.eval::("shared(42).is_shared()")?); - - #[cfg(not(feature = "no_index"))] - { - assert_eq!( - engine.eval::( - r#" - let s = shared("test"); - let i = shared(0); - i = 2; - s[i] = 'S'; - - s - "# - )?, - "teSt" - ); - - assert_eq!( - engine - .eval::( - r#" - let x = shared([1, 2, 3]); - let y = shared([4, 5]); - x + y - "# - )? - .len(), - 5 - ); - - #[cfg(not(feature = "no_object"))] - assert_eq!( - engine.eval::( - r" - let x = shared([2, 9]); - x.insert(-1, 1); - x.insert(999, 3); - - let r = x.remove(2); - - let y = shared([4, 5]); - x.append(y); - - x.len + r - " - )?, - 14 - ); - - assert_eq!( - engine.eval::( - r#" - let x = shared([1, 2, 3]); - - if x[0] + x[2] == 4 { - true - } else { - false - } - "# - )?, - true - ); - - #[cfg(not(feature = "no_function"))] - #[cfg(not(feature = "no_object"))] - assert_eq!( - engine.eval::( - r#" - let x = shared([1, 2, 3]); - let y = shared(()); - - (|| { - for i in x { - y = i * 10; - } - }).call(); - - y - "# - )?, - 30 - ); - } - - #[cfg(not(feature = "no_object"))] - assert_eq!( - engine.eval::( - r#" - let y = shared(#{a: 1, b: 2, c: 3}); - y.c = shared(5); - y.c - "# - )?, - 5 - ); - - #[cfg(not(feature = "no_object"))] - assert_eq!( - engine.eval::( - r#" - let y = shared(#{a: 1, b: 2, c: shared(3)}); - let c = y.c; - c = 5;// "c" holds Dynamic Shared - y.c - "# - )?, - 5 - ); - - #[cfg(not(feature = "no_function"))] - #[cfg(not(feature = "no_capture"))] - assert_eq!( - engine.eval::( - r#" - let x = shared(1); - (|| x = x + 41).call(); - x - "# - )?, - 42 - ); - - #[cfg(not(feature = "no_function"))] - #[cfg(not(feature = "no_object"))] - #[cfg(not(feature = "no_capture"))] - assert_eq!( - engine.eval::( - r#" - let x = shared(#{a: 1, b: shared(2), c: 3}); - let a = x.a; - let b = x.b; - a = 100; // does not hold reference to x.a - b = 20; // does hold reference to x.b - - let f = |a| { - x.c = x.a + x.b + a; - }; - - f.call(21); - - x.c - "# - )?, - 42 - ); - - // Register a binary function named `foo` - engine.register_fn("custom_addition", |x: INT, y: INT| x + y); - - assert_eq!( - engine.eval::("custom_addition(shared(20), shared(22))")?, - 42 - ); - - #[cfg(not(feature = "no_object"))] - { - #[derive(Clone)] - struct TestStruct { - x: INT, - } - - impl TestStruct { - fn update(&mut self) { - self.x += 1000; - } - - fn merge(&mut self, other: Self) { - self.x += other.x; - } - - fn get_x(&mut self) -> INT { - self.x - } - - fn set_x(&mut self, new_x: INT) { - self.x = new_x; - } - - fn new() -> Self { - TestStruct { x: 1 } - } - } - - engine - .register_type::() - .register_get_set("x", TestStruct::get_x, TestStruct::set_x) - .register_fn("update", TestStruct::update) - .register_fn("merge", TestStruct::merge) - .register_fn("new_ts", TestStruct::new) - .register_raw_fn( - "mutate_with_cb", - &[ - TypeId::of::(), - TypeId::of::(), - TypeId::of::(), - ], - move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { - let fp = std::mem::take(args[2]).cast::(); - let mut value = args[1].clone(); - { - let mut lock = value.write_lock::().unwrap(); - *lock = *lock + 1; - } - let this_ptr = args.get_mut(0).unwrap(); - - fp.call_dynamic(engine, lib, Some(this_ptr), [value]) - }, - ); - - assert_eq!( - engine.eval::( - r" - let a = shared(new_ts()); - - a.x = 100; - a.update(); - a.merge(a.take()); // take is important to prevent a deadlock - - a.x - " - )?, - 2200 - ); - - assert_eq!( - engine.eval::( - r" - let a = shared(new_ts()); - let b = shared(100); - - a.mutate_with_cb(b, |param| { - this.x = param; - param = 50; - this.update(); - }); - - a.update(); - a.x += b; - - a.x - " - )?, - 2151 - ); - } - - Ok(()) -} - -#[test] -fn test_shared_data_race() -> Result<(), Box> { - let engine = Engine::new(); - - #[cfg(not(feature = "no_function"))] - #[cfg(not(feature = "no_object"))] - { - assert_eq!( - engine.eval::( - r#" - fn foo(x) { this += x }; - - let a = shared(41); - a.foo(1); - a - "# - )?, - 42 - ); - - assert!(matches!( - *engine - .eval::( - r#" - fn foo(x) { this += x }; - - let a = shared(42); - a.foo(a); - a - "# - ) - .expect_err("should error"), - EvalAltResult::ErrorDataRace(_, _) - )); - } - - Ok(()) -} From dddd8133df659086431d0d6df28c0cf2dbad2305 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 3 Aug 2020 12:38:56 +0800 Subject: [PATCH 55/64] Set unshared value in let/const statement. --- src/engine.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index be915025..7b99dd3d 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1764,7 +1764,10 @@ impl Engine { Stmt::Let(x) if x.1.is_some() => { let ((var_name, _), expr, _) = x.as_ref(); let expr = expr.as_ref().unwrap(); - let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + let val = self + .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? + .clone_inner_data() + .unwrap(); let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state); scope.push_dynamic_value(var_name, ScopeEntryType::Normal, val, false); Ok(Default::default()) @@ -1780,7 +1783,10 @@ impl Engine { // Const statement Stmt::Const(x) if x.1.is_constant() => { let ((var_name, _), expr, _) = x.as_ref(); - let val = self.eval_expr(scope, mods, state, lib, this_ptr, &expr, level)?; + let val = self + .eval_expr(scope, mods, state, lib, this_ptr, &expr, level)? + .clone_inner_data() + .unwrap(); let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state); scope.push_dynamic_value(var_name, ScopeEntryType::Constant, val, true); Ok(Default::default()) From e1a70fe9585bb143545dd511602127f47d438886 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 3 Aug 2020 23:11:08 +0800 Subject: [PATCH 56/64] Add FnPtr to standard packages. --- src/packages/array_basic.rs | 7 ++++--- src/packages/string_basic.rs | 2 +- src/packages/string_more.rs | 5 +++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index c246f1d5..5574a4f5 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -3,6 +3,7 @@ use crate::any::{Dynamic, Variant}; use crate::def_package; use crate::engine::{Array, Engine}; +use crate::fn_native::FnPtr; use crate::module::{FuncReturn, Module}; use crate::parser::{ImmutableString, INT}; @@ -83,9 +84,9 @@ macro_rules! reg_pad { } def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { - reg_op!(lib, "push", push, INT, bool, char, ImmutableString, Array, ()); - reg_pad!(lib, "pad", pad, INT, bool, char, ImmutableString, Array, ()); - reg_tri!(lib, "insert", ins, INT, bool, char, ImmutableString, Array, ()); + reg_op!(lib, "push", push, INT, bool, char, ImmutableString, FnPtr, Array, ()); + reg_pad!(lib, "pad", pad, INT, bool, char, ImmutableString, FnPtr, Array, ()); + reg_tri!(lib, "insert", ins, INT, bool, char, ImmutableString, FnPtr, Array, ()); lib.set_fn_2_mut("append", |x: &mut Array, y: Array| { x.extend(y); diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 36f4f333..08778150 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -21,7 +21,7 @@ fn to_debug(x: &mut T) -> FuncReturn { Ok(format!("{:?}", x).into()) } fn to_string(x: &mut T) -> FuncReturn { - Ok(format!("{}", x).into()) + Ok(x.to_string().into()) } #[cfg(not(feature = "no_object"))] fn format_map(x: &mut Map) -> FuncReturn { diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 130af8b8..149823c4 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -1,6 +1,7 @@ use crate::any::Dynamic; use crate::def_package; use crate::engine::Engine; +use crate::fn_native::FnPtr; use crate::module::{FuncReturn, Module}; use crate::parser::{ImmutableString, INT}; use crate::utils::StaticVec; @@ -88,10 +89,10 @@ macro_rules! reg_op { } def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, { - reg_op!(lib, "+", append, INT, bool, char); + reg_op!(lib, "+", append, INT, bool, char, FnPtr); lib.set_fn_2( "+", |x: ImmutableString, _: ()| Ok(x)); - reg_op!(lib, "+", prepend, INT, bool, char); + reg_op!(lib, "+", prepend, INT, bool, char, FnPtr); lib.set_fn_2("+", |_: (), y: ImmutableString| Ok(y)); if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { From abe74e7f44e8722f7578dec2efcf73190220a095 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 3 Aug 2020 23:11:24 +0800 Subject: [PATCH 57/64] Handle Dynamic::from(FnPtr). --- src/any.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/any.rs b/src/any.rs index 78d681be..f9c8a093 100644 --- a/src/any.rs +++ b/src/any.rs @@ -551,6 +551,11 @@ impl Dynamic { } } + boxed = match unsafe_cast_box::<_, FnPtr>(boxed) { + Ok(fn_ptr) => return (*fn_ptr).into(), + Err(val) => val, + }; + boxed = match unsafe_cast_box::<_, Dynamic>(boxed) { Ok(d) => return *d, Err(val) => val, @@ -805,6 +810,7 @@ impl Dynamic { let data = cell.read().unwrap(); let type_id = (*data).type_id(); + println!("Type = {}", (*data).type_name()); if type_id != TypeId::of::() && TypeId::of::() != TypeId::of::() { None @@ -1176,7 +1182,7 @@ impl, T: Variant + Clone> From> for Dynam } impl From for Dynamic { fn from(value: FnPtr) -> Self { - Box::new(value).into() + Self(Union::FnPtr(Box::new(value))) } } impl From> for Dynamic { From 3b6d35368f228d0b79656220c94de1196b96ced1 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 3 Aug 2020 23:11:38 +0800 Subject: [PATCH 58/64] Handle shared for loop variable. --- src/engine.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/engine.rs b/src/engine.rs index 7b99dd3d..65228a8f 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1701,7 +1701,15 @@ impl Engine { state.scope_level += 1; for loop_var in func(iter_type) { - *scope.get_mut(index).0 = loop_var; + let for_var = scope.get_mut(index).0; + let value = loop_var.clone_inner_data().unwrap(); + + if cfg!(not(feature = "no_closure")) && for_var.is_shared() { + *for_var.write_lock().unwrap() = value; + } else { + *for_var = value; + } + self.inc_operations(state) .map_err(|err| err.new_position(stmt.position()))?; From 2aa08c0dd95d8d340fdd6fe09396c53c42ae67c7 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 3 Aug 2020 23:12:42 +0800 Subject: [PATCH 59/64] get_constant_value for FnPointer. --- src/parser.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 7f29bbcd..272215ac 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -6,7 +6,7 @@ use crate::engine::{ Engine, KEYWORD_FN_PTR_CURRY, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT, }; use crate::error::{LexError, ParseError, ParseErrorType}; -use crate::fn_native::Shared; +use crate::fn_native::{FnPtr, Shared}; use crate::module::{Module, ModuleRef}; use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::scope::{EntryType as ScopeEntryType, Scope}; @@ -835,6 +835,10 @@ impl Expr { Self::FloatConstant(x) => x.0.into(), Self::CharConstant(x) => x.0.into(), Self::StringConstant(x) => x.0.clone().into(), + Self::FnPointer(x) => Dynamic(Union::FnPtr(Box::new(FnPtr::new_unchecked( + x.0.clone(), + Default::default(), + )))), Self::True(_) => true.into(), Self::False(_) => false.into(), Self::Unit(_) => ().into(), @@ -3292,7 +3296,7 @@ fn parse_anon_fn( let hash = s.finish(); // Create unique function name - let fn_name: ImmutableString = format!("{}{:16x}", FN_ANONYMOUS, hash).into(); + let fn_name: ImmutableString = format!("{}{:016x}", FN_ANONYMOUS, hash).into(); // Define the function let script = ScriptFnDef { From 4a7bf893e643ee0f2e4069ffac144c6ddc6db6b4 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 3 Aug 2020 23:13:09 +0800 Subject: [PATCH 60/64] Refine flatten clone for scope. --- src/scope.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/scope.rs b/src/scope.rs index 836108c7..61f6fa17 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -4,9 +4,7 @@ use crate::any::{Dynamic, Variant}; use crate::parser::{map_dynamic_to_expr, Expr}; use crate::token::Position; -use crate::stdlib::{ - borrow::Cow, boxed::Box, collections::HashMap, iter, string::String, vec::Vec, -}; +use crate::stdlib::{borrow::Cow, boxed::Box, iter, string::String, vec::Vec}; /// Type of an entry in the Scope. #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] @@ -394,15 +392,19 @@ impl<'a> Scope<'a> { /// Clone the Scope, keeping only the last instances of each variable name. /// Shadowed variables are omitted in the copy. pub(crate) fn flatten_clone(&self) -> Self { - let mut entries: HashMap<&str, Entry> = Default::default(); + let mut entries: Vec = Default::default(); self.0.iter().rev().for_each(|entry| { - entries - .entry(entry.name.as_ref()) - .or_insert_with(|| entry.clone()); + if entries + .iter() + .find(|Entry { name, .. }| &entry.name == name) + .is_none() + { + entries.push(entry.clone()); + } }); - Self(entries.into_iter().map(|(_, v)| v).collect()) + Self(entries) } /// Get an iterator to entries in the Scope. From 3d6c83c6d81e62bd746af1d979fc594a4fc1cacf Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 4 Aug 2020 09:47:48 +0800 Subject: [PATCH 61/64] Fix serde builds. --- src/any.rs | 53 +++++++++++++++++++++++++++++++------------------ src/serde/de.rs | 3 +++ 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/any.rs b/src/any.rs index f9c8a093..bdde78f3 100644 --- a/src/any.rs +++ b/src/any.rs @@ -337,7 +337,10 @@ impl Dynamic { Union::Variant(value) => (***value).type_name(), #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] - Union::Shared(cell) => (*cell.borrow()).type_name(), + Union::Shared(cell) => cell + .try_borrow() + .map(|v| (*v).type_name()) + .unwrap_or(""), #[cfg(not(feature = "no_closure"))] #[cfg(feature = "sync")] Union::Shared(cell) => (*cell.read().unwrap()).type_name(), @@ -394,10 +397,20 @@ impl fmt::Display for Dynamic { Union::FnPtr(value) => fmt::Display::fmt(value, f), #[cfg(not(feature = "no_std"))] - Union::Variant(value) if value.is::() => write!(f, ""), - Union::Variant(value) => write!(f, "{}", (*value).type_name()), + Union::Variant(value) if value.is::() => f.write_str(""), + Union::Variant(value) => f.write_str((*value).type_name()), #[cfg(not(feature = "no_closure"))] - Union::Shared(_) => f.write_str(""), + #[cfg(not(feature = "sync"))] + Union::Shared(cell) => { + if let Ok(v) = cell.try_borrow() { + fmt::Display::fmt(&*v, f) + } else { + f.write_str("") + } + } + #[cfg(not(feature = "no_closure"))] + #[cfg(feature = "sync")] + Union::Shared(cell) => fmt::Display::fmt(*cell.read_lock().unwrap(), f), } } } @@ -425,7 +438,17 @@ impl fmt::Debug for Dynamic { Union::Variant(value) if value.is::() => write!(f, ""), Union::Variant(value) => write!(f, "{}", (*value).type_name()), #[cfg(not(feature = "no_closure"))] - Union::Shared(_) => f.write_str(""), + #[cfg(not(feature = "sync"))] + Union::Shared(cell) => { + if let Ok(v) = cell.try_borrow() { + write!(f, "{:?} (shared)", *v) + } else { + f.write_str("") + } + } + #[cfg(not(feature = "no_closure"))] + #[cfg(feature = "sync")] + Union::Shared(cell) => fmt::Display::fmt(*cell.read_lock().unwrap(), f), } } } @@ -861,13 +884,9 @@ impl Dynamic { /// Get a reference of a specific type to the `Dynamic`. /// Casting to `Dynamic` just returns a reference to it. /// - /// Returns `None` if the cast fails. - /// - /// # Panics - /// - /// Panics if the value is shared. + /// Returns `None` if the cast fails, or if the value is shared. #[inline(always)] - fn downcast_ref(&self) -> Option<&T> { + pub(crate) fn downcast_ref(&self) -> Option<&T> { let type_id = TypeId::of::(); if type_id == TypeId::of::() { @@ -940,7 +959,7 @@ impl Dynamic { match &self.0 { Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::(), #[cfg(not(feature = "no_closure"))] - Union::Shared(_) => unreachable!(), + Union::Shared(_) => None, _ => None, } } @@ -948,13 +967,9 @@ impl Dynamic { /// Get a mutable reference of a specific type to the `Dynamic`. /// Casting to `Dynamic` just returns a mutable reference to it. /// - /// Returns `None` if the cast fails. - /// - /// # Panics - /// - /// Panics if the value is shared. + /// Returns `None` if the cast fails, or if the value is shared. #[inline(always)] - fn downcast_mut(&mut self) -> Option<&mut T> { + pub(crate) fn downcast_mut(&mut self) -> Option<&mut T> { let type_id = TypeId::of::(); if type_id == TypeId::of::() { @@ -1021,7 +1036,7 @@ impl Dynamic { match &mut self.0 { Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::(), #[cfg(not(feature = "no_closure"))] - Union::Shared(_) => unreachable!(), + Union::Shared(_) => None, _ => None, } } diff --git a/src/serde/de.rs b/src/serde/de.rs index 8a60604c..f6d328db 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -177,6 +177,9 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { Union::Variant(value) if value.is::() => self.deserialize_u128(visitor), Union::Variant(_) => self.type_error(), + + #[cfg(not(feature = "no_closure"))] + Union::Shared(_) => self.type_error(), } } From 4878a695030e91e4454e0fb67449c6ba91fb01cf Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 4 Aug 2020 16:27:55 +0800 Subject: [PATCH 62/64] Add docs for closures. --- Cargo.toml | 2 +- RELEASES.md | 4 +- doc/src/SUMMARY.md | 2 +- doc/src/about/features.md | 2 + doc/src/language/fn-anon.md | 5 +- doc/src/language/fn-capture.md | 13 ++- doc/src/language/fn-closure.md | 154 ++++++++++++++++++++++++--- doc/src/language/fn-curry.md | 2 +- doc/src/language/values-and-types.md | 32 +++--- doc/src/links.md | 4 +- doc/src/rust/register-raw.md | 18 ++-- doc/src/start/features.md | 2 +- src/any.rs | 24 ++--- 13 files changed, 200 insertions(+), 64 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7d20a93c..f6ffdea4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ smallvec = { version = "1.4.1", default-features = false } [features] #default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"] default = [] -plugins = [] +plugins = [] # custom plugins support unchecked = [] # unchecked arithmetic sync = [] # restrict to only types that implement Send + Sync no_optimize = [] # no script optimizer diff --git a/RELEASES.md b/RELEASES.md index 79f298ec..1d2a842b 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -9,7 +9,7 @@ This version adds: * Binding the `this` pointer in a function pointer `call`. * Anonymous functions (in Rust closure syntax). Simplifies creation of single-use ad-hoc functions. * Currying of function pointers. -* Auto-currying of anonymous functions. +* Closures - auto-currying of anonymous functions to capture shared variables from the external scope. * Capturing call scope via `func!(...)` syntax. New features @@ -21,7 +21,7 @@ New features * Anonymous functions are supported in the syntax of a Rust closure, e.g. `|x, y, z| x + y - z`. * Custom syntax now works even without the `internals` feature. * Currying of function pointers is supported via the new `curry` keyword. -* Automatic currying of anonymous functions to capture environment variables. +* Automatic currying of anonymous functions to capture shared variables from the external scope. * Capturing of the calling scope for function call via the `func!(...)` syntax. * `Module::set_indexer_get_set_fn` is added as a shorthand of both `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn`. * New `unicode-xid-ident` feature to allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) for identifiers. diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index 6b966de1..3b23b321 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -79,7 +79,7 @@ The Rhai Scripting Language 4. [Function Pointers](language/fn-ptr.md) 5. [Anonymous Functions](language/fn-anon.md) 6. [Currying](language/fn-curry.md) - 7. [Capturing External Variables](language/fn-closure.md) + 7. [Closures](language/fn-closure.md) 16. [Print and Debug](language/print-debug.md) 17. [Modules](language/modules/index.md) 1. [Export Variables, Functions and Sub-Modules](language/modules/export.md) diff --git a/doc/src/about/features.md b/doc/src/about/features.md index 245668b2..6c2e3965 100644 --- a/doc/src/about/features.md +++ b/doc/src/about/features.md @@ -37,6 +37,8 @@ Dynamic * Dynamic dispatch via [function pointers] with additional support for [currying]. +* Closures via [automatic currying] with capturing shared variables from the external scope. + * Some support for [object-oriented programming (OOP)][OOP]. Safe diff --git a/doc/src/language/fn-anon.md b/doc/src/language/fn-anon.md index b8a9154d..8b514509 100644 --- a/doc/src/language/fn-anon.md +++ b/doc/src/language/fn-anon.md @@ -55,5 +55,6 @@ WARNING - NOT Real Closures Remember: anonymous functions, though having the same syntax as Rust _closures_, are themselves **not** real closures. -In particular, they capture their execution environment via [automatic currying][capture], -unless the [`no_closure`] feature is turned on. + +In particular, they capture their execution environment via [automatic currying] +(disabled via [`no_closure`]). diff --git a/doc/src/language/fn-capture.md b/doc/src/language/fn-capture.md index 042675e9..08052826 100644 --- a/doc/src/language/fn-capture.md +++ b/doc/src/language/fn-capture.md @@ -16,6 +16,8 @@ it raises an evaluation error. It is possible, through a special syntax, to capture the calling scope - i.e. the scope that makes the function call - and access variables defined there. +Capturing can be disabled via the [`no_closure`] feature. + ```rust fn foo(y) { // function accesses 'x' and 'y', but 'x' is not defined x += y; // 'x' is modified in this function @@ -47,7 +49,7 @@ f.call!(41); // <- syntax error: capturing is not allowed in method-c No Mutations ------------ -Variables in the calling scope are accessed as copies. +Variables in the calling scope are captured as copies. Changes to them do not reflect back to the calling scope. Rhai functions remain _pure_ in the sense that they can never mutate their environment. @@ -56,7 +58,10 @@ Rhai functions remain _pure_ in the sense that they can never mutate their envir Caveat Emptor ------------- -Functions relying on the calling scope is a _Very Bad Idea™_ because it makes code almost impossible -to reason and maintain, as their behaviors are volatile and unpredictable. +Functions relying on the calling scope is often a _Very Bad Idea™_ because it makes code +almost impossible to reason and maintain, as their behaviors are volatile and unpredictable. -This usage should be at the last resort. +They behave more like macros that are expanded inline than actual function calls, thus the +syntax is also similar to Rust's macro invocations. + +This usage should be at the last resort. YOU HAVE BEEN WARNED. diff --git a/doc/src/language/fn-closure.md b/doc/src/language/fn-closure.md index 9ba66f2e..2f6ce2c5 100644 --- a/doc/src/language/fn-closure.md +++ b/doc/src/language/fn-closure.md @@ -1,10 +1,10 @@ -Capture External Variables via Automatic Currying -================================================ +Simulating Closures +=================== {{#include ../links.md}} -Poor Man's Closures -------------------- +Capture External Variables via Automatic Currying +------------------------------------------------ Since [anonymous functions] de-sugar to standard function definitions, they retain all the behaviors of Rhai functions, including being _pure_, having no access to external variables. @@ -15,13 +15,23 @@ is created. Variables that are accessible during the time the [anonymous function] is created can be captured, as long as they are not shadowed by local variables defined within the function's scope. -The captured variables are automatically converted into reference-counted shared values. + +The captured variables are automatically converted into **reference-counted shared values** +(`Rc>` in normal builds, `Arc>` in [`sync`] builds). + +Therefore, similar to closures in many languages, these captured shared values persist through +reference counting, and may be read or modified even after the variables that hold them +go out of scope and no longer exist. + +Use the `is_shared` function to check whether a particular value is a shared value. + +Automatic currying can be turned off via the [`no_closure`] feature. -New Parameters For Captured Variables ------------------------------------- +Actual Implementation +--------------------- -In actual implementation, this de-sugars to: +The actual implementation de-sugars to: 1. Keeping track of what variables are accessed inside the anonymous function, @@ -29,32 +39,148 @@ In actual implementation, this de-sugars to: 3. The variable is added to the parameters list of the anonymous function, at the front. -4. The variable is then turned into a reference-counted shared value. +4. The variable is then converted into a **reference-counted shared value**. + + An [anonymous function] which captures an external variable is the only way to create a reference-counted shared value in Rhai. 5. The shared value is then [curried][currying] into the [function pointer] itself, essentially carrying a reference to that shared value and inserting it into future calls of the function. -Automatic currying can be turned off via the [`no_closure`] feature. + This process is called _Automatic Currying_, and is the mechanism through which Rhai simulates normal closures. Examples -------- ```rust -let x = 1; +let x = 1; // a normal variable let f = |y| x + y; // variable 'x' is auto-curried (captured) into 'f' - // 'x' is converted into a shared value -x = 40; // 'x' can be changed +x.is_shared() == true; // 'x' is now a shared value! + +x = 40; // changing 'x'... f.call(2) == 42; // the value of 'x' is 40 because 'x' is shared // The above de-sugars into this: fn anon$1001(x, y) { x + y } // parameter 'x' is inserted -make_shared(x); // convert 'x' into a shared value +make_shared(x); // convert variable 'x' into a shared value let f = Fn("anon$1001").curry(x); // shared 'x' is curried f.call(2) == 42; ``` + + +Beware: Captured Variables are Truly Shared +------------------------------------------ + +The example below is a typical tutorial sample for many languages to illustrate the traps +that may accompany capturing external scope variables in closures. + +It prints `9`, `9`, `9`, ... `9`, `9`, not `0`, `1`, `2`, ... `8`, `9`, because there is +ever only one captured variable, and all ten closures capture the _same_ variable. + +```rust +let funcs = []; + +for i in range(0, 10) { + funcs.push(|| print(i)); // the for loop variable 'i' is captured +} + +funcs.len() == 10; // 10 closures stored in the array + +funcs[0].type_of() == "Fn"; // make sure these are closures + +for f in funcs { + f.call(); // all the references to 'i' are the same variable! +} +``` + + +Therefore - Be Careful to Prevent Data Races +------------------------------------------- + +Rust does not have data races, but that doesn't mean Rhai doesn't. + +Avoid performing a method call on a captured shared variable (which essentially takes a +mutable reference to the shared object) while using that same variable as a parameter +in the method call - this is a sure-fire way to generate a data race error. + +If a shared value is used as the `this` pointer in a method call to a closure function, +then the same shared value _must not_ be captured inside that function, or a data race +will occur and the script will terminate with an error. + +```rust +let x = 20; + +let f = |a| this += x + a; // 'x' is captured in this closure + +x.is_shared() == true; // now 'x' is shared + +x.call(f, 2); // <- error: data race detected on 'x' +``` + + +Data Races in `sync` Builds Can Become Deadlocks +----------------------------------------------- + +Under the [`sync`] feature, shared values are guarded with a `RwLock`, meaning that data race +conditions no longer raise an error. + +Instead, they wait endlessly for the `RwLock` to be freed, and thus can become deadlocks. + +On the other hand, since the same thread (i.e. the [`Engine`] thread) that is holding the lock +is attempting to read it again, this may also [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1) +depending on the O/S. + +```rust +let x = 20; + +let f = |a| this += x + a; // 'x' is captured in this closure + +// Under `sync`, the following may wait forever, or may panic, +// because 'x' is locked as the `this` pointer but also accessed +// via a captured shared value. +x.call(f, 2); +``` + + +TL;DR +----- + +### Q: Why are closures implemented as automatic currying? + +In concept, a closure _closes_ over captured variables from the outer scope - that's why +they are called _closures_. When this happen, a typical language implementation hoists +those variables that are captured away from the stack frame and into heap-allocated storage. +This is because those variables may be needed after the stack frame goes away. + +These heap-allocated captured variables only go away when all the closures that need them +are finished with them. A garbage collector makes this trivial to implement - they are +automatically collected as soon as all closures needing them are destroyed. + +In Rust, this can be done by reference counting instead, with the potential pitfall of creating +reference loops that will prevent those variables from being deallocated forever. +Rhai avoids this by clone-copying most data values, so reference loops are hard to create. + +Rhai does the hoisting of captured variables into the heap by converting those values +into reference-counted locked values, also allocated on the heap. The process is identical. + +Closures are usually implemented as a data structure containing two items: + +1) A function pointer to the function body of the closure, +2) A data structure containing references to the captured shared variables on the heap. + +Usually a language implementation passes the structure containing references to captured +shared variables into the function pointer, the function body taking this data structure +as an additional parameter. + +This is essentially what Rhai does, except that Rhai passes each variable individually +as separate parameters to the function, instead of creating a structure and passing that +structure as a single parameter. This is the only difference. + +Therefore, in most languages, essentially all closures are implemented as automatic currying of +shared variables hoisted into the heap, automatically passing those variables as parameters into +the function. Rhai just brings this directly up to the front. diff --git a/doc/src/language/fn-curry.md b/doc/src/language/fn-curry.md index 83705175..c223d8cd 100644 --- a/doc/src/language/fn-curry.md +++ b/doc/src/language/fn-curry.md @@ -33,7 +33,7 @@ curried.call(2) == 42; // <- de-sugars to 'func.call(21, 2)' Automatic Currying ------------------ -[Anonymous functions] defined via a closure syntax _capture_ the _values_ of external variables +[Anonymous functions] defined via a closure syntax _capture_ external variables that are not shadowed inside the function's scope. This is accomplished via [automatic currying]. diff --git a/doc/src/language/values-and-types.md b/doc/src/language/values-and-types.md index 833eead5..f7446913 100644 --- a/doc/src/language/values-and-types.md +++ b/doc/src/language/values-and-types.md @@ -5,22 +5,22 @@ Values and Types The following primitive types are supported natively: -| Category | Equivalent Rust types | [`type_of()`] | `to_string()` | -| --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------- | ----------------------- | -| **Integer number** | `u8`, `i8`, `u16`, `i16`,
`u32`, `i32` (default for [`only_i32`]),
`u64`, `i64` _(default)_ | `"i32"`, `"u64"` etc. | `"42"`, `"123"` etc. | -| **Floating-point number** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | `"f32"` or `"f64"` | `"123.4567"` etc. | -| **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` | -| **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. | -| **Immutable Unicode [string]** | `rhai::ImmutableString` (implemented as `Rc` or `Arc`) | `"string"` | `"hello"` etc. | -| **[`Array`]** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` | -| **[Object map]** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `"#{ "a": 1, "b": 2 }"` | -| **[Timestamp]** (implemented in the [`BasicTimePackage`][packages], disabled with [`no_std`]) | `std::time::Instant` ([`instant::Instant`] if [WASM] build) | `"timestamp"` | `""` | -| **[Function pointer]** | `rhai::FnPtr` | `Fn` | `"Fn(foo)"` | -| **[`Dynamic`] value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ | -| **Shared value** (a reference-counted, shared [`Dynamic`] value) | | _the actual type_ | _actual value_ | -| **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. | -| **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. | -| **Nothing/void/nil/null/Unit** (or whatever it is called) | `()` | `"()"` | `""` _(empty string)_ | +| Category | Equivalent Rust types | [`type_of()`] | `to_string()` | +| -------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------- | ----------------------- | +| **Integer number** | `u8`, `i8`, `u16`, `i16`,
`u32`, `i32` (default for [`only_i32`]),
`u64`, `i64` _(default)_ | `"i32"`, `"u64"` etc. | `"42"`, `"123"` etc. | +| **Floating-point number** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | `"f32"` or `"f64"` | `"123.4567"` etc. | +| **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` | +| **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. | +| **Immutable Unicode [string]** | `rhai::ImmutableString` (implemented as `Rc` or `Arc`) | `"string"` | `"hello"` etc. | +| **[`Array`]** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` | +| **[Object map]** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `"#{ "a": 1, "b": 2 }"` | +| **[Timestamp]** (implemented in the [`BasicTimePackage`][packages], disabled with [`no_std`]) | `std::time::Instant` ([`instant::Instant`] if [WASM] build) | `"timestamp"` | `""` | +| **[Function pointer]** | `rhai::FnPtr` | `Fn` | `"Fn(foo)"` | +| **[`Dynamic`] value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ | +| **Shared value** (a reference-counted, shared [`Dynamic`] value, created via [automatic currying], disabled with [`no_closure`]) | | _the actual type_ | _actual value_ | +| **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. | +| **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. | +| **Nothing/void/nil/null/Unit** (or whatever it is called) | `()` | `"()"` | `""` _(empty string)_ | All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different - they even cannot be added together. This is very similar to Rust. diff --git a/doc/src/links.md b/doc/src/links.md index 8667082c..11b16796 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -79,8 +79,10 @@ [function pointer]: {{rootUrl}}/language/fn-ptr.md [function pointers]: {{rootUrl}}/language/fn-ptr.md [currying]: {{rootUrl}}/language/fn-curry.md -[capture]: {{rootUrl}}/language/fn-closure.md +[capture]: {{rootUrl}}/language/fn-capture.md [automatic currying]: {{rootUrl}}/language/fn-closure.md +[closure]: {{rootUrl}}/language/fn-closure.md +[closures]: {{rootUrl}}/language/fn-closure.md [function namespace]: {{rootUrl}}/language/fn-namespaces.md [function namespaces]: {{rootUrl}}/language/fn-namespaces.md [anonymous function]: {{rootUrl}}/language/fn-anon.md diff --git a/doc/src/rust/register-raw.md b/doc/src/rust/register-raw.md index cf997286..b666d588 100644 --- a/doc/src/rust/register-raw.md +++ b/doc/src/rust/register-raw.md @@ -35,12 +35,12 @@ engine.register_raw_fn( // Therefore, get a '&mut' reference to the first argument _last_. // Alternatively, use `args.split_at_mut(1)` etc. to split the slice first. - let y: i64 = *args[1].downcast_ref::() // get a reference to the second argument + let y: i64 = *args[1].read_lock::() // get a reference to the second argument .unwrap(); // then copying it because it is a primary type let y: i64 = std::mem::take(args[1]).cast::(); // alternatively, directly 'consume' it - let x: &mut i64 = args[0].downcast_mut::() // get a '&mut' reference to the + let x: &mut i64 = args[0].write_lock::() // get a '&mut' reference to the .unwrap(); // first argument *x += y; // perform the action @@ -84,12 +84,12 @@ Extract Arguments To extract an argument from the `args` parameter (`&mut [&mut Dynamic]`), use the following: -| Argument type | Access (`n` = argument position) | Result | -| ------------------------------ | -------------------------------------- | ---------------------------------------------------------- | -| [Primary type][standard types] | `args[n].clone().cast::()` | Copy of value. | -| Custom type | `args[n].downcast_ref::().unwrap()` | Immutable reference to value. | -| Custom type (consumed) | `std::mem::take(args[n]).cast::()` | The _consumed_ value.
The original value becomes `()`. | -| `this` object | `args[0].downcast_mut::().unwrap()` | Mutable reference to value. | +| Argument type | Access (`n` = argument position) | Result | +| ------------------------------ | ------------------------------------- | ---------------------------------------------------------- | +| [Primary type][standard types] | `args[n].clone().cast::()` | Copy of value. | +| Custom type | `args[n].read_lock::().unwrap()` | Immutable reference to value. | +| Custom type (consumed) | `std::mem::take(args[n]).cast::()` | The _consumed_ value.
The original value becomes `()`. | +| `this` object | `args[0].write_lock::().unwrap()` | Mutable reference to value. | When there is a mutable reference to the `this` object (i.e. the first argument), there can be no other immutable references to `args`, otherwise the Rust borrow checker will complain. @@ -156,5 +156,5 @@ let this_ptr = first[0].downcast_mut::
().unwrap(); // Immutable reference to the second value parameter // This can be mutable but there is no point because the parameter is passed by value -let value = rest[0].downcast_ref::().unwrap(); +let value_ref = rest[0].read_lock::().unwrap(); ``` diff --git a/doc/src/start/features.md b/doc/src/start/features.md index 3b8cee04..3201aa07 100644 --- a/doc/src/start/features.md +++ b/doc/src/start/features.md @@ -23,7 +23,7 @@ more control over what a script can (or cannot) do. | `no_object` | Disable support for [custom types] and [object maps]. | | `no_function` | Disable script-defined [functions]. | | `no_module` | Disable loading external [modules]. | -| `no_closure` | Disable [capturing][capture] external variables in [anonymous functions] to simulate _closures_, or [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls. | +| `no_closure` | Disable [capturing][automatic currying] external variables in [anonymous functions] to simulate _closures_, or [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls. | | `no_std` | Build for `no-std` (implies `no_closure`). Notice that additional dependencies will be pulled in to replace `std` features. | | `serde` | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. | | `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. | diff --git a/src/any.rs b/src/any.rs index bdde78f3..5efcad16 100644 --- a/src/any.rs +++ b/src/any.rs @@ -283,9 +283,9 @@ impl Dynamic { /// Get the TypeId of the value held by this `Dynamic`. /// - /// # Panics and Deadlocks When Value is Shared + /// # Panics or Deadlocks When Value is Shared /// - /// Under the `sync` feature, this call may deadlock. + /// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1). /// Otherwise, this call panics if the data is currently borrowed for write. pub fn type_id(&self) -> TypeId { match &self.0 { @@ -313,9 +313,9 @@ impl Dynamic { /// Get the name of the type of the value held by this `Dynamic`. /// - /// # Panics and Deadlocks When Value is Shared + /// # Panics or Deadlocks When Value is Shared /// - /// Under the `sync` feature, this call may deadlock. + /// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1). /// Otherwise, this call panics if the data is currently borrowed for write. pub fn type_name(&self) -> &'static str { match &self.0 { @@ -621,9 +621,9 @@ impl Dynamic { /// /// Returns `None` if types mismatched. /// - /// # Panics and Deadlocks + /// # Panics or Deadlocks /// - /// Under the `sync` feature, this call may deadlock. + /// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1). /// Otherwise, this call panics if the data is currently borrowed for write. /// /// These normally shouldn't occur since most operations in Rhai is single-threaded. @@ -744,12 +744,12 @@ impl Dynamic { /// /// Returns `None` if types mismatched. /// - /// # Panics and Deadlocks + /// # Panics or Deadlocks /// /// Panics if the cast fails (e.g. the type of the actual value is not the /// same as the specified type). /// - /// Under the `sync` feature, this call may deadlock. + /// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1). /// Otherwise, this call panics if the data is currently borrowed for write. /// /// These normally shouldn't occur since most operations in Rhai is single-threaded. @@ -817,9 +817,9 @@ impl Dynamic { /// /// Returns `None` if the cast fails. /// - /// # Panics and Deadlocks When Value is Shared + /// # Panics or Deadlocks When Value is Shared /// - /// Under the `sync` feature, this call may deadlock. + /// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1). /// Otherwise, this call panics if the data is currently borrowed for write. #[inline(always)] pub fn read_lock(&self) -> Option> { @@ -852,9 +852,9 @@ impl Dynamic { /// /// Returns `None` if the cast fails. /// - /// # Panics and Deadlocks When Value is Shared + /// # Panics or Deadlocks When Value is Shared /// - /// Under the `sync` feature, this call may deadlock. + /// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1). /// Otherwise, this call panics if the data is currently borrowed for write. #[inline(always)] pub fn write_lock(&mut self) -> Option> { From 06a9ca205742ebfa81b4dbcb66875579fbcf90ee Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 4 Aug 2020 17:21:10 +0800 Subject: [PATCH 63/64] Delete benchmark.yml --- .github/workflows/benchmark.yml | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 .github/workflows/benchmark.yml diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml deleted file mode 100644 index df310705..00000000 --- a/.github/workflows/benchmark.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Benchmark -on: - push: - branches: - - master - -jobs: - benchmark: - name: Run Rust benchmark - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - run: rustup toolchain update nightly && rustup default nightly - - name: Run benchmark - run: cargo +nightly bench | tee output.txt - - name: Store benchmark result - uses: rhysd/github-action-benchmark@v1 - with: - name: Rust Benchmark - tool: 'cargo' - output-file-path: output.txt - # Use personal access token instead of GITHUB_TOKEN due to https://github.community/t5/GitHub-Actions/Github-action-not-triggering-gh-pages-upon-push/td-p/26869/highlight/false - github-token: ${{ secrets.RHAI }} - auto-push: true - # Show alert with commit comment on detecting possible performance regression - alert-threshold: '200%' - comment-on-alert: true - fail-on-alert: true - alert-comment-cc-users: '@schungx' From ac90b7f246769f51e6189f75e02e7dceed54897f Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 4 Aug 2020 17:24:22 +0800 Subject: [PATCH 64/64] Revert "Delete benchmark.yml" This reverts commit 06a9ca205742ebfa81b4dbcb66875579fbcf90ee. --- .github/workflows/benchmark.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/benchmark.yml diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 00000000..df310705 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,29 @@ +name: Benchmark +on: + push: + branches: + - master + +jobs: + benchmark: + name: Run Rust benchmark + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: rustup toolchain update nightly && rustup default nightly + - name: Run benchmark + run: cargo +nightly bench | tee output.txt + - name: Store benchmark result + uses: rhysd/github-action-benchmark@v1 + with: + name: Rust Benchmark + tool: 'cargo' + output-file-path: output.txt + # Use personal access token instead of GITHUB_TOKEN due to https://github.community/t5/GitHub-Actions/Github-action-not-triggering-gh-pages-upon-push/td-p/26869/highlight/false + github-token: ${{ secrets.RHAI }} + auto-push: true + # Show alert with commit comment on detecting possible performance regression + alert-threshold: '200%' + comment-on-alert: true + fail-on-alert: true + alert-comment-cc-users: '@schungx'