From 6db412815b74b1d2ade3b1d152e5b83078bcbb4e Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 13 Oct 2020 14:39:49 +0800 Subject: [PATCH 01/17] Add chop and extract for arrays. --- RELEASES.md | 2 +- doc/src/language/arrays.md | 125 ++++++++++++++++++------------------ src/packages/array_basic.rs | 39 +++++++++++ 3 files changed, 102 insertions(+), 64 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 73fd61d2..f144c309 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -31,7 +31,7 @@ New features * `is_def_var()` to detect if variable is defined, and `is_def_fn()` to detect if script function is defined. * `Dynamic::from(&str)` now constructs a `Dynamic` with a copy of the string as value. * `AST::combine` and `AST::combine_filtered` allows combining two `AST`'s without creating a new one. -* `map`, `filter`, `reduce`, `reduce_rev`, `some`, `all`, `splice` and `sort` functions for arrays. +* `map`, `filter`, `reduce`, `reduce_rev`, `some`, `all`, `extract`, `splice`, `chop` and `sort` functions for arrays. Enhancements ------------ diff --git a/doc/src/language/arrays.md b/doc/src/language/arrays.md index 437364d6..ba7e4b7a 100644 --- a/doc/src/language/arrays.md +++ b/doc/src/language/arrays.md @@ -30,30 +30,32 @@ Built-in Functions The following methods (mostly defined in the [`BasicArrayPackage`][packages] but excluded if using a [raw `Engine`]) operate on arrays: -| Function | Parameter(s) | Description | -| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `push` | element to insert | inserts an element at the end | -| `append` | array to append | concatenates the second array to the end of the first | -| `+=` operator | 1) array
2) element to insert (not another array) | inserts an element at the end | -| `+=` operator | 1) array
2) array to append | concatenates the second array to the end of the first | -| `+` operator | 1) first array
2) second array | concatenates the first array with the second | -| `insert` | 1) element to insert
2) position (beginning if <= 0, end if >= length) | inserts an element at a certain index | -| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) | -| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) | -| `remove` | index | removes an element at a particular index and returns it ([`()`] if the index is not valid) | -| `reverse` | _none_ | reverses the array | -| `len` method and property | _none_ | returns the number of elements | -| `pad` | 1) target length
2) element to pad | pads the array with an element to at least a specified length | -| `clear` | _none_ | empties the array | -| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) | -| `splice` | 1) start position (beginning if <= 0, end if >= length),
2) number of items to remove (none if <= 0),
3) array to insert | replaces a portion of the array with another (not necessarily of the same length as the replaced portion) | -| `filter` | [function pointer] to predicate (can be a [closure]) | constructs a new array with all items that returns `true` when called with the predicate function:
1st parameter: array item,
2nd parameter: offset index (optional) | -| `map` | [function pointer] to conversion function (can be a [closure]) | constructs a new array with all items mapped to the result of applying the conversion function:
1st parameter: array item,
2nd parameter: offset index (optional) | -| `reduce` | [function pointer] to accumulator function (can be a [closure]) | constructs a new array with all items accumulated by the accumulator function:
1st parameter: accumulated value ([`()`] initially),
2nd parameter: array item,
3rd parameter: offset index (optional) | -| `reduce_rev` | [function pointer] to accumulator function (can be a [closure]) | constructs a new array with all items (in reverse order) accumulated by the accumulator function:
1st parameter: accumulated value ([`()`] initially),
2nd parameter: array item,
3rd parameter: offset index (optional) | -| `some` | [function pointer] to predicate (can be a [closure]) | returns `true` if any item returns `true` when called with the predicate function:
1st parameter: array item,
2nd parameter: offset index (optional) | -| `all` | [function pointer] to predicate (can be a [closure]) | returns `true` if all item returns `true` when called with the predicate function:
1st parameter: array item,
2nd parameter: offset index (optional) | -| `sort` | [function pointer] to a comparison function (can be a [closure]) | sorts the array with a comparison function:
1st parameter: first item,
2nd parameter: second item | +| Function | Parameter(s) | Description | +| ------------------------- | --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `push` | element to insert | inserts an element at the end | +| `append` | array to append | concatenates the second array to the end of the first | +| `+=` operator | 1) array
2) element to insert (not another array) | inserts an element at the end | +| `+=` operator | 1) array
2) array to append | concatenates the second array to the end of the first | +| `+` operator | 1) first array
2) second array | concatenates the first array with the second | +| `insert` | 1) element to insert
2) position, beginning if < 0, end if > length | inserts an element at a certain index | +| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) | +| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) | +| `extract` | 1) start position, beginning if < 0, end if > length,
2) number of items to extract, none if < 0 _(optional)_ | extracts a portion of the array into a new array | +| `remove` | index | removes an element at a particular index and returns it ([`()`] if the index is not valid) | +| `reverse` | _none_ | reverses the array | +| `len` method and property | _none_ | returns the number of elements | +| `pad` | 1) target length
2) element to pad | pads the array with an element to at least a specified length | +| `clear` | _none_ | empties the array | +| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) | +| `chop` | target length | cuts off the head of the array, leaving the tail at exactly a specified length | +| `splice` | 1) start position, beginning if < 0, end if > length,
2) number of items to remove, none if < 0,
3) array to insert | replaces a portion of the array with another (not necessarily of the same length as the replaced portion) | +| `filter` | [function pointer] to predicate (can be a [closure]) | constructs a new array with all items that returns `true` when called with the predicate function:
1st parameter: array item,
2nd parameter: offset index _(optional)_ | +| `map` | [function pointer] to conversion function (can be a [closure]) | constructs a new array with all items mapped to the result of applying the conversion function:
1st parameter: array item,
2nd parameter: offset index _(optional)_ | +| `reduce` | [function pointer] to accumulator function (can be a [closure]) | reduces the array into a single value via the accumulator function:
1st parameter: accumulated value ([`()`] initially),
2nd parameter: array item,
3rd parameter: offset index _(optional)_ | +| `reduce_rev` | [function pointer] to accumulator function (can be a [closure]) | reduces the array (in reverse order) into a single value via the accumulator function:
1st parameter: accumulated value ([`()`] initially),
2nd parameter: array item,
3rd parameter: offset index _(optional)_ | +| `some` | [function pointer] to predicate (can be a [closure]) | returns `true` if any item returns `true` when called with the predicate function:
1st parameter: array item,
2nd parameter: offset index _(optional)_ | +| `all` | [function pointer] to predicate (can be a [closure]) | returns `true` if all item returns `true` when called with the predicate function:
1st parameter: array item,
2nd parameter: offset index _(optional)_ | +| `sort` | [function pointer] to a comparison function (can be a [closure]) | sorts the array with a comparison function:
1st parameter: first item,
2nd parameter: second item,
return value: `INT` < 0 if first < second, > 0 if first > second, 0 if first == second | Use Custom Types With Arrays @@ -71,12 +73,13 @@ Examples -------- ```rust -let y = [2, 3]; // array literal with 2 elements +let y = [2, 3]; // y == [2, 3] -let y = [2, 3,]; // trailing comma is OK +let y = [2, 3,]; // y == [2, 3] -y.insert(0, 1); // insert element at the beginning -y.insert(999, 4); // insert element at the end +y.insert(0, 1); // y == [1, 2, 3] + +y.insert(999, 4); // y == [1, 2, 3, 4] y.len == 4; @@ -89,71 +92,69 @@ y[3] == 4; (42 in y) == false; // 'in' uses the '==' operator (which users can override) // to check if the target item exists in the array -y[1] = 42; // array elements can be reassigned +y[1] = 42; // y == [1, 42, 3, 4] (42 in y) == true; -y.remove(2) == 3; // remove element +y.remove(2) == 3; // y == [1, 42, 4] y.len == 3; y[2] == 4; // elements after the removed element are shifted ts.list = y; // arrays can be assigned completely (by value copy) -let foo = ts.list[1]; -foo == 42; -let foo = [1, 2, 3][0]; -foo == 1; +ts.list[1] == 42; + +[1, 2, 3][0] == 1; // indexing on array literal fn abc() { [42, 43, 44] // a function returning an array } -let foo = abc()[0]; -foo == 42; +abc()[0] == 42; -let foo = y[0]; -foo == 1; +y.push(4); // y == [1, 42, 4, 4] -y.push(4); // 4 elements -y += 5; // 5 elements +y += 5; // y == [1, 42, 4, 4, 5] y.len == 5; -let first = y.shift(); // remove the first element, 4 elements remaining -first == 1; +y.shift() == 1; // y == [42, 4, 4, 5] -let last = y.pop(); // remove the last element, 3 elements remaining -last == 5; +y.chop(3); // y == [4, 4, 5] y.len == 3; +y.pop() == 5; // y == [4, 4] + +y.len == 2; + for item in y { // arrays can be iterated with a 'for' statement print(item); } -y.pad(10, "hello"); // pad the array up to 10 elements +y.pad(6, "hello"); // y == [4, 4, "hello", "hello", "hello", "hello"] -y.len == 10; +y.len == 6; -y.truncate(5); // truncate the array to 5 elements +y.truncate(4); // y == [4, 4, "hello", "hello"] -y.len == 5; +y.len == 4; -y.clear(); // empty the array +y.clear(); // y == [] y.len == 0; let a = [42, 123, 99]; -a.map(|v| v + 1); // [43, 124, 100] +a.map(|v| v + 1); // returns [43, 124, 100] -a.map(|v, i| v + i); // [42, 124, 101] +a.map(|v, i| v + i); // returns [42, 124, 101] -a.filter(|v| v > 50); // [123, 99] +a.filter(|v| v > 50); // returns [123, 99] -a.filter(|v, i| i == 1); // [123] +a.filter(|v, i| i == 1); // returns [123] a.reduce(|sum, v| { // Detect the initial value of '()' @@ -170,22 +171,20 @@ a.reduce_rev(|sum, v| { ) == 264; a.reduce_rev(|sum, v, i| { - if i == 0 { v } else { sum + v } + if i == 2 { v } else { sum + v } ) == 264; -a.some(|v| v > 50) == true; +a.some(|v| v > 50); // returns true -a.some(|v, i| v < i) == false; +a.some(|v, i| v < i); // returns false -a.all(|v| v > 50) == false; +a.all(|v| v > 50); // returns false -a.all(|v, i| v > i) == true; +a.all(|v, i| v > i); // returns true -a.splice(1, 1, [1, 3, 2]); +a.splice(1, 1, [1, 3, 2]); // a == [42, 1, 3, 2, 99] -a == [42, 1, 3, 2, 99]; +a.extract(1, 3); // returns [1, 3, 2] -a.sort(|x, y| x - y); - -a == [1, 2, 3, 42, 99]; +a.sort(|x, y| x - y); // a == [1, 2, 3, 42, 99] ``` diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 107ed85e..117bf2c8 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -131,6 +131,14 @@ mod array_functions { list.clear(); } } + pub fn chop(list: &mut Array, len: INT) { + if len as usize >= list.len() { + } else if len >= 0 { + list.drain(0..list.len() - len as usize); + } else { + list.clear(); + } + } pub fn reverse(list: &mut Array) { list.reverse(); } @@ -153,6 +161,37 @@ mod array_functions { list.splice(start..start + len, replace.into_iter()); } + pub fn extract(list: &mut Array, start: INT, len: INT) -> Array { + let start = if start < 0 { + 0 + } else if start as usize >= list.len() { + list.len() - 1 + } else { + start as usize + }; + + let len = if len < 0 { + 0 + } else if len as usize > list.len() - start { + list.len() - start + } else { + len as usize + }; + + list[start..start + len].iter().cloned().collect() + } + #[rhai_fn(name = "extract")] + pub fn extract_tail(list: &mut Array, start: INT) -> Array { + let start = if start < 0 { + 0 + } else if start as usize >= list.len() { + list.len() - 1 + } else { + start as usize + }; + + list[start..].iter().cloned().collect() + } } fn pad( From 28697e0380f6672cd4f6f4c650109fb4f69ab01b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 13 Oct 2020 14:40:02 +0800 Subject: [PATCH 02/17] Remove support for script-line native functions. --- src/fn_call.rs | 21 +++++++++------------ src/module/mod.rs | 30 ------------------------------ 2 files changed, 9 insertions(+), 42 deletions(-) diff --git a/src/fn_call.rs b/src/fn_call.rs index 437a86dc..62c2ca28 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -193,11 +193,9 @@ impl Engine { // Search for the native function // First search registered functions (can override packages) // Then search packages - let func = self - .global_module - .get_fn(hash_fn, pub_only) - .or_else(|| lib.get_fn(hash_fn, pub_only)) - .or_else(|| self.packages.get_fn(hash_fn, pub_only)); + let func = //lib.get_fn(hash_fn, pub_only) + self.global_module.get_fn(hash_fn, pub_only) + .or_else(|| self.packages.get_fn(hash_fn, pub_only)); if let Some(func) = func { assert!(func.is_native()); @@ -462,9 +460,9 @@ impl Engine { // First check script-defined functions lib.contains_fn(hash_script, pub_only) - || lib.contains_fn(hash_fn, pub_only) + //|| lib.contains_fn(hash_fn, pub_only) // Then check registered functions - || self.global_module.contains_fn(hash_script, pub_only) + //|| self.global_module.contains_fn(hash_script, pub_only) || self.global_module.contains_fn(hash_fn, pub_only) // Then check packages || self.packages.contains_fn(hash_script, pub_only) @@ -547,15 +545,14 @@ impl Engine { // Script-like function found #[cfg(not(feature = "no_function"))] - _ if self.global_module.contains_fn(hash_script, pub_only) - || lib.contains_fn(hash_script, pub_only) + _ if lib.contains_fn(hash_script, pub_only) + //|| self.global_module.contains_fn(hash_script, pub_only) || self.packages.contains_fn(hash_script, pub_only) => { // Get function - let func = self - .global_module + let func = lib .get_fn(hash_script, pub_only) - .or_else(|| lib.get_fn(hash_script, pub_only)) + //.or_else(|| self.global_module.get_fn(hash_script, pub_only)) .or_else(|| self.packages.get_fn(hash_script, pub_only)) .unwrap(); diff --git a/src/module/mod.rs b/src/module/mod.rs index afa82cfe..566b678d 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -549,36 +549,6 @@ impl Module { ) } - /// Set a raw function but with a signature that is a scripted function (meaning that the types - /// are not determined), but the implementation is in Rust. - #[cfg(not(feature = "no_function"))] - #[cfg(not(feature = "no_module"))] - #[inline] - #[allow(dead_code)] - pub(crate) fn set_raw_fn_as_scripted( - &mut self, - name: impl Into, - num_params: usize, - func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn + SendSync + 'static, - ) -> u64 { - // None + function name + number of arguments. - let name = name.into(); - let hash_script = calc_fn_hash(empty(), &name, num_params, empty()); - let f = move |engine: &Engine, lib: &Module, args: &mut FnCallArgs| func(engine, lib, args); - self.functions.insert( - hash_script, - ( - name, - FnAccess::Public, - num_params, - None, - CallableFunction::from_pure(Box::new(f)), - ), - ); - self.indexed = false; - hash_script - } - /// Set a Rust function taking no parameters into the module, returning a hash key. /// /// If there is a similar existing Rust function, it is replaced. From 037e8334d4df07bc21cece9e883c60723561fd46 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 13 Oct 2020 15:49:09 +0800 Subject: [PATCH 03/17] Reset modulo precedence. --- doc/src/engine/custom-op.md | 4 ++-- src/token.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/src/engine/custom-op.md b/doc/src/engine/custom-op.md index 2e98ecc1..bc8ff83f 100644 --- a/doc/src/engine/custom-op.md +++ b/doc/src/engine/custom-op.md @@ -95,8 +95,8 @@ The following _precedence table_ show the built-in precedence of standard Rhai o | Comparisons | `>`, `>=`, `<`, `<=` | 110 | | | `in` | 130 | | Arithmetic | `+`, `-` | 150 | -| Arithmetic | `*`, `/` | 180 | -| Arithmetic | `~`, `%` | 190 | +| Arithmetic | `*`, `/`, `%` | 180 | +| Arithmetic | `~` | 190 | | Bit-shifts | `<<`, `>>` | 210 | | Object | `.` _(binds to right)_ | 240 | | _Others_ | | 0 | diff --git a/src/token.rs b/src/token.rs index fde6a703..428067ef 100644 --- a/src/token.rs +++ b/src/token.rs @@ -623,9 +623,9 @@ impl Token { Plus | Minus => 150, - Divide | Multiply => 180, + Divide | Multiply | Modulo => 180, - PowerOf | Modulo => 190, + PowerOf => 190, LeftShift | RightShift => 210, From 9dceeaf114b17608ff0c3f91712ea60eaaaf9d6b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 13 Oct 2020 15:51:59 +0800 Subject: [PATCH 04/17] Reserve ** operator. --- doc/src/appendix/operators.md | 1 + src/token.rs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/doc/src/appendix/operators.md b/doc/src/appendix/operators.md index 7f20f003..ff9b7fed 100644 --- a/doc/src/appendix/operators.md +++ b/doc/src/appendix/operators.md @@ -60,6 +60,7 @@ Symbols and Patterns | `--` | decrement | _reserved_ | | `..` | range | _reserved_ | | `...` | range | _reserved_ | +| `**` | exponentiation | _reserved_ | | `#` | hash | _reserved_ | | `@` | at | _reserved_ | | `$` | dollar | _reserved_ | diff --git a/src/token.rs b/src/token.rs index 428067ef..eb2d63f3 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1231,6 +1231,10 @@ fn get_next_token_inner( ('-', _) if !state.non_unary => return Some((Token::UnaryMinus, start_pos)), ('-', _) => return Some((Token::Minus, start_pos)), + ('*', '*') => { + eat_next(stream, pos); + return Some((Token::Reserved("**".into()), start_pos)); + } ('*', ')') => { eat_next(stream, pos); return Some((Token::Reserved("*)".into()), start_pos)); From 13c4d0bbb35b42db21ee07269841ca19c3c60c71 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 13 Oct 2020 16:01:42 +0800 Subject: [PATCH 05/17] Adjust precedence of in. --- RELEASES.md | 2 +- doc/src/engine/custom-op.md | 16 ++++++++-------- src/token.rs | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index f144c309..6f9e4ed9 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -20,7 +20,7 @@ Breaking changes * New reserved symbols: `++`, `--`, `..`, `...`. * Callback signature for custom syntax implementation function is changed to allow for more flexibility. * Default call stack depth for `debug` builds is reduced to 12 (from 16). -* Precedence for `~` and `%` is raised. +* Precedence for `~` is raised, while `in` is moved below logic comparison operators. New features ------------ diff --git a/doc/src/engine/custom-op.md b/doc/src/engine/custom-op.md index bc8ff83f..fa2b1487 100644 --- a/doc/src/engine/custom-op.md +++ b/doc/src/engine/custom-op.md @@ -84,7 +84,11 @@ Operator Precedence All operators in Rhai has a _precedence_ indicating how tightly they bind. -The following _precedence table_ show the built-in precedence of standard Rhai operators: +A higher precedence binds more tightly than a lower precedence, so `*` and `/` binds before `+` and `-` etc. + +When registering a custom operator, the operator's precedence must also be provided. + +The following _precedence table_ shows the built-in precedence of standard Rhai operators: | Category | Operators | Precedence (0-255) | | ------------------- | :-------------------------------------------------------------------------------------: | :----------------: | @@ -92,15 +96,11 @@ The following _precedence table_ show the built-in precedence of standard Rhai o | Logic and bit masks | \|\|, \|, `^` | 30 | | Logic and bit masks | `&`, `&&` | 60 | | Comparisons | `==`, `!=` | 90 | -| Comparisons | `>`, `>=`, `<`, `<=` | 110 | -| | `in` | 130 | +| | `in` | 110 | +| Comparisons | `>`, `>=`, `<`, `<=` | 130 | | Arithmetic | `+`, `-` | 150 | | Arithmetic | `*`, `/`, `%` | 180 | | Arithmetic | `~` | 190 | | Bit-shifts | `<<`, `>>` | 210 | | Object | `.` _(binds to right)_ | 240 | -| _Others_ | | 0 | - -A higher precedence binds more tightly than a lower precedence, so `*` and `/` binds before `+` and `-` etc. - -When registering a custom operator, the operator's precedence must also be provided. +| Unary operators | unary `+`, `-`, `!` _(binds to right)_ | 255 | diff --git a/src/token.rs b/src/token.rs index eb2d63f3..d4e04c02 100644 --- a/src/token.rs +++ b/src/token.rs @@ -617,9 +617,9 @@ impl Token { EqualsTo | NotEqualsTo => 90, - LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo => 110, + In => 110, - In => 130, + LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo => 10, Plus | Minus => 150, From 3df8d6c398e1b9ad84136c99cd9ba859ef5ea8ba Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 13 Oct 2020 17:16:19 +0800 Subject: [PATCH 06/17] Fix typo. --- src/token.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/token.rs b/src/token.rs index d4e04c02..92f10da8 100644 --- a/src/token.rs +++ b/src/token.rs @@ -619,7 +619,7 @@ impl Token { In => 110, - LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo => 10, + LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo => 130, Plus | Minus => 150, From e0c39edff4b81404b45aed6e6fe220e562501c08 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 14 Oct 2020 21:27:31 +0800 Subject: [PATCH 07/17] Enhance array functions. --- doc/src/language/arrays.md | 83 ++++++++++++++---------- doc/src/language/string-fn.md | 28 ++++---- src/packages/array_basic.rs | 119 ++++++++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+), 49 deletions(-) diff --git a/doc/src/language/arrays.md b/doc/src/language/arrays.md index ba7e4b7a..7016a19c 100644 --- a/doc/src/language/arrays.md +++ b/doc/src/language/arrays.md @@ -30,32 +30,33 @@ Built-in Functions The following methods (mostly defined in the [`BasicArrayPackage`][packages] but excluded if using a [raw `Engine`]) operate on arrays: -| Function | Parameter(s) | Description | -| ------------------------- | --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `push` | element to insert | inserts an element at the end | -| `append` | array to append | concatenates the second array to the end of the first | -| `+=` operator | 1) array
2) element to insert (not another array) | inserts an element at the end | -| `+=` operator | 1) array
2) array to append | concatenates the second array to the end of the first | -| `+` operator | 1) first array
2) second array | concatenates the first array with the second | -| `insert` | 1) element to insert
2) position, beginning if < 0, end if > length | inserts an element at a certain index | -| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) | -| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) | -| `extract` | 1) start position, beginning if < 0, end if > length,
2) number of items to extract, none if < 0 _(optional)_ | extracts a portion of the array into a new array | -| `remove` | index | removes an element at a particular index and returns it ([`()`] if the index is not valid) | -| `reverse` | _none_ | reverses the array | -| `len` method and property | _none_ | returns the number of elements | -| `pad` | 1) target length
2) element to pad | pads the array with an element to at least a specified length | -| `clear` | _none_ | empties the array | -| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) | -| `chop` | target length | cuts off the head of the array, leaving the tail at exactly a specified length | -| `splice` | 1) start position, beginning if < 0, end if > length,
2) number of items to remove, none if < 0,
3) array to insert | replaces a portion of the array with another (not necessarily of the same length as the replaced portion) | -| `filter` | [function pointer] to predicate (can be a [closure]) | constructs a new array with all items that returns `true` when called with the predicate function:
1st parameter: array item,
2nd parameter: offset index _(optional)_ | -| `map` | [function pointer] to conversion function (can be a [closure]) | constructs a new array with all items mapped to the result of applying the conversion function:
1st parameter: array item,
2nd parameter: offset index _(optional)_ | -| `reduce` | [function pointer] to accumulator function (can be a [closure]) | reduces the array into a single value via the accumulator function:
1st parameter: accumulated value ([`()`] initially),
2nd parameter: array item,
3rd parameter: offset index _(optional)_ | -| `reduce_rev` | [function pointer] to accumulator function (can be a [closure]) | reduces the array (in reverse order) into a single value via the accumulator function:
1st parameter: accumulated value ([`()`] initially),
2nd parameter: array item,
3rd parameter: offset index _(optional)_ | -| `some` | [function pointer] to predicate (can be a [closure]) | returns `true` if any item returns `true` when called with the predicate function:
1st parameter: array item,
2nd parameter: offset index _(optional)_ | -| `all` | [function pointer] to predicate (can be a [closure]) | returns `true` if all item returns `true` when called with the predicate function:
1st parameter: array item,
2nd parameter: offset index _(optional)_ | -| `sort` | [function pointer] to a comparison function (can be a [closure]) | sorts the array with a comparison function:
1st parameter: first item,
2nd parameter: second item,
return value: `INT` < 0 if first < second, > 0 if first > second, 0 if first == second | +| Function | Parameter(s) | Description | +| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `push` | element to insert | inserts an element at the end | +| `append` | array to append | concatenates the second array to the end of the first | +| `+=` operator | 1) array
2) element to insert (not another array) | inserts an element at the end | +| `+=` operator | 1) array
2) array to append | concatenates the second array to the end of the first | +| `+` operator | 1) first array
2) second array | concatenates the first array with the second | +| `insert` | 1) element to insert
2) position, beginning if < 0, end if > length | inserts an element at a certain index | +| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) | +| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) | +| `extract` | 1) start position, beginning if < 0, end if > length,
2) _(optional)_ number of items to extract, none if < 0 | extracts a portion of the array into a new array | +| `remove` | index | removes an element at a particular index and returns it ([`()`] if the index is not valid) | +| `reverse` | _none_ | reverses the array | +| `len` method and property | _none_ | returns the number of elements | +| `pad` | 1) target length
2) element to pad | pads the array with an element to at least a specified length | +| `clear` | _none_ | empties the array | +| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) | +| `chop` | target length | cuts off the head of the array, leaving the tail at exactly a specified length | +| `splice` | 1) start position, beginning if < 0, end if > length,
2) number of items to remove, none if < 0,
3) array to insert | replaces a portion of the array with another (not necessarily of the same length as the replaced portion) | +| `filter` | [function pointer] to predicate (usually a [closure]) | constructs a new array with all items that returns `true` when called with the predicate function:
1st parameter: array item,
2nd parameter: _(optional)_ offset index | +| `map` | [function pointer] to conversion function (usually a [closure]) | constructs a new array with all items mapped to the result of applying the conversion function:
1st parameter: array item,
2nd parameter: _(optional)_ offset index | +| `reduce` | 1) [function pointer] to accumulator function (usually a [closure]),
2) _(optional)_ [function pointer] to function (usually a [closure]) that provides the initial value | reduces the array into a single value via the accumulator function:
1st parameter: accumulated value ([`()`] initially),
2nd parameter: array item,
3rd parameter: _(optional)_ offset index | +| `reduce_rev` | 1) [function pointer] to accumulator function (usually a [closure]),
2) _(optional)_ [function pointer] to function (usually a [closure]) that provides the initial value | reduces the array (in reverse order) into a single value via the accumulator function:
1st parameter: accumulated value ([`()`] initially),
2nd parameter: array item,
3rd parameter: _(optional)_ offset index | +| `some` | [function pointer] to predicate (usually a [closure]) | returns `true` if any item returns `true` when called with the predicate function:
1st parameter: array item,
2nd parameter: _(optional)_ offset index | +| `none` | [function pointer] to predicate (usually a [closure]) | returns `true` if no item returns `true` when called with the predicate function:
1st parameter: array item,
2nd parameter: _(optional)_ offset index | +| `all` | [function pointer] to predicate (usually a [closure]) | returns `true` if all items return `true` when called with the predicate function:
1st parameter: array item,
2nd parameter: _(optional)_ offset index | +| `sort` | [function pointer] to a comparison function (usually a [closure]) | sorts the array with a comparison function:
1st parameter: first item,
2nd parameter: second item,
return value: `INT` < 0 if first < second, > 0 if first > second, 0 if first == second | Use Custom Types With Arrays @@ -156,21 +157,29 @@ a.filter(|v| v > 50); // returns [123, 99] a.filter(|v, i| i == 1); // returns [123] -a.reduce(|sum, v| { - // Detect the initial value of '()' - if sum.type_of() == "()" { v } else { sum + v } +// Use a closure to provide the initial value +a.reduce(|sum, v| sum + v, || 0) == 264; + +// Detect the initial value of '()' +a.reduce( + |sum, v| if sum.type_of() == "()" { v } else { sum + v } ) == 264; -a.reduce(|sum, v, i| { +// Detect the initial value via index +a.reduce(|sum, v, i| if i == 0 { v } else { sum + v } ) == 264; -a.reduce_rev(|sum, v| { - // Detect the initial value of '()' - if sum.type_of() == "()" { v } else { sum + v } +// Use a closure to provide the initial value +a.reduce_rev(|sum, v| sum + v, || 0) == 264; + +// Detect the initial value of '()' +a.reduce_rev( + |sum, v| if sum.type_of() == "()" { v } else { sum + v } ) == 264; -a.reduce_rev(|sum, v, i| { +// Detect the initial value via index +a.reduce_rev(|sum, v, i| if i == 2 { v } else { sum + v } ) == 264; @@ -178,6 +187,10 @@ a.some(|v| v > 50); // returns true a.some(|v, i| v < i); // returns false +a.none(|v| v != 0); // returns false + +a.none(|v, i| v == i); // returns true + a.all(|v| v > 50); // returns false a.all(|v, i| v > i); // returns true @@ -186,5 +199,5 @@ a.splice(1, 1, [1, 3, 2]); // a == [42, 1, 3, 2, 99] a.extract(1, 3); // returns [1, 3, 2] -a.sort(|x, y| x - y); // a == [1, 2, 3, 42, 99] +a.sort(|x, y| x - y); // a == [1, 2, 3, 42, 99] ``` diff --git a/doc/src/language/string-fn.md b/doc/src/language/string-fn.md index d6e373a5..00efd8e1 100644 --- a/doc/src/language/string-fn.md +++ b/doc/src/language/string-fn.md @@ -6,20 +6,20 @@ Built-in String Functions The following standard methods (mostly defined in the [`MoreStringPackage`][packages] but excluded if using a [raw `Engine`]) operate on [strings]: -| Function | Parameter(s) | Description | -| ------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -| `len` method and property | _none_ | returns the number of characters (not number of bytes) in the string | -| `pad` | 1) character to pad
2) target length | pads the string with an character to at least a specified length | -| `+=` operator, `append` | character/string to append | Adds a character or a string to the end of another string | -| `clear` | _none_ | empties the string | -| `truncate` | target length | cuts off the string at exactly a specified number of characters | -| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string | -| `index_of` | 1) character/sub-string to search for
2) start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found | -| `sub_string` | 1) start index
2) length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) | -| `split` | delimiter character/string | splits the string by the specified delimiter, returning an [array] of string segments; not available under [`no_index`] | -| `crop` | 1) start index
2) length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) | -| `replace` | 1) target character/sub-string
2) replacement character/string | replaces a sub-string with another | -| `trim` | _none_ | trims the string of whitespace at the beginning and end | +| Function | Parameter(s) | Description | +| ------------------------- | ------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | +| `len` method and property | _none_ | returns the number of characters (not number of bytes) in the string | +| `pad` | 1) character to pad
2) target length | pads the string with an character to at least a specified length | +| `+=` operator, `append` | character/string to append | Adds a character or a string to the end of another string | +| `clear` | _none_ | empties the string | +| `truncate` | target length | cuts off the string at exactly a specified number of characters | +| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string | +| `index_of` | 1) character/sub-string to search for
2) _(optional)_ start index | returns the index that a certain character or sub-string occurs in the string, or -1 if not found | +| `sub_string` | 1) start index
2) _(optional)_ number of characters to extract, none if < 0 | extracts a sub-string (to the end of the string if length is not specified) | +| `split` | delimiter character/string | splits the string by the specified delimiter, returning an [array] of string segments; not available under [`no_index`] | +| `crop` | 1) start index
2) _(optional)_ number of characters to retain, none if < 0 | retains only a portion of the string | +| `replace` | 1) target character/sub-string
2) replacement character/string | replaces a sub-string with another | +| `trim` | _none_ | trims the string of whitespace at the beginning and end | Examples -------- diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 117bf2c8..d688ef3a 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -74,9 +74,12 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { lib.set_raw_fn("map", &[TypeId::of::(), TypeId::of::()], map); lib.set_raw_fn("filter", &[TypeId::of::(), TypeId::of::()], filter); lib.set_raw_fn("reduce", &[TypeId::of::(), TypeId::of::()], reduce); + lib.set_raw_fn("reduce", &[TypeId::of::(), TypeId::of::(), TypeId::of::()], reduce_with_initial); lib.set_raw_fn("reduce_rev", &[TypeId::of::(), TypeId::of::()], reduce_rev); + lib.set_raw_fn("reduce_rev", &[TypeId::of::(), TypeId::of::(), TypeId::of::()], reduce_rev_with_initial); lib.set_raw_fn("some", &[TypeId::of::(), TypeId::of::()], some); lib.set_raw_fn("all", &[TypeId::of::(), TypeId::of::()], all); + lib.set_raw_fn("none", &[TypeId::of::(), TypeId::of::()], none); lib.set_raw_fn("sort", &[TypeId::of::(), TypeId::of::()], sort); // Merge in the module at the end to override `+=` for arrays @@ -364,6 +367,40 @@ fn all( Ok(true.into()) } +fn none( + engine: &Engine, + lib: &Module, + args: &mut [&mut Dynamic], +) -> Result> { + let list = args[0].read_lock::().unwrap(); + let filter = args[1].read_lock::().unwrap(); + + for (i, item) in list.iter().enumerate() { + if filter + .call_dynamic(engine, lib, None, [item.clone()]) + .or_else(|err| match *err { + EvalAltResult::ErrorFunctionNotFound(_, _) => { + filter.call_dynamic(engine, lib, None, [item.clone(), (i as INT).into()]) + } + _ => Err(err), + }) + .map_err(|err| { + Box::new(EvalAltResult::ErrorInFunctionCall( + "filter".to_string(), + err, + Position::none(), + )) + })? + .as_bool() + .unwrap_or(false) + { + return Ok(false.into()); + } + } + + Ok(true.into()) +} + fn reduce( engine: &Engine, lib: &Module, @@ -398,6 +435,47 @@ fn reduce( Ok(result) } +fn reduce_with_initial( + engine: &Engine, + lib: &Module, + args: &mut [&mut Dynamic], +) -> Result> { + let list = args[0].read_lock::().unwrap(); + let reducer = args[1].read_lock::().unwrap(); + let initial = args[2].read_lock::().unwrap(); + + let mut result = initial.call_dynamic(engine, lib, None, []).map_err(|err| { + Box::new(EvalAltResult::ErrorInFunctionCall( + "reduce".to_string(), + err, + Position::none(), + )) + })?; + + for (i, item) in list.iter().enumerate() { + result = reducer + .call_dynamic(engine, lib, None, [result.clone(), item.clone()]) + .or_else(|err| match *err { + EvalAltResult::ErrorFunctionNotFound(_, _) => reducer.call_dynamic( + engine, + lib, + None, + [result, item.clone(), (i as INT).into()], + ), + _ => Err(err), + }) + .map_err(|err| { + Box::new(EvalAltResult::ErrorInFunctionCall( + "reduce".to_string(), + err, + Position::none(), + )) + })?; + } + + Ok(result) +} + fn reduce_rev( engine: &Engine, lib: &Module, @@ -432,6 +510,47 @@ fn reduce_rev( Ok(result) } +fn reduce_rev_with_initial( + engine: &Engine, + lib: &Module, + args: &mut [&mut Dynamic], +) -> Result> { + let list = args[0].read_lock::().unwrap(); + let reducer = args[1].read_lock::().unwrap(); + let initial = args[2].read_lock::().unwrap(); + + let mut result = initial.call_dynamic(engine, lib, None, []).map_err(|err| { + Box::new(EvalAltResult::ErrorInFunctionCall( + "reduce".to_string(), + err, + Position::none(), + )) + })?; + + for (i, item) in list.iter().enumerate().rev() { + result = reducer + .call_dynamic(engine, lib, None, [result.clone(), item.clone()]) + .or_else(|err| match *err { + EvalAltResult::ErrorFunctionNotFound(_, _) => reducer.call_dynamic( + engine, + lib, + None, + [result, item.clone(), (i as INT).into()], + ), + _ => Err(err), + }) + .map_err(|err| { + Box::new(EvalAltResult::ErrorInFunctionCall( + "reduce".to_string(), + err, + Position::none(), + )) + })?; + } + + Ok(result) +} + fn sort( engine: &Engine, lib: &Module, From 707ece7e80dad9f67e29d15c4e77bec83f1961c6 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 14 Oct 2020 23:22:10 +0800 Subject: [PATCH 08/17] Refactor iterators API. --- RELEASES.md | 2 ++ src/api.rs | 10 ++++++---- src/engine.rs | 15 ++++++++------- src/lib.rs | 2 +- src/module/mod.rs | 18 ++++++++++++++++++ src/packages/array_basic.rs | 5 +---- src/packages/iter_basic.rs | 18 ++++-------------- src/packages/string_more.rs | 4 +--- 8 files changed, 41 insertions(+), 33 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 6f9e4ed9..4ce09906 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -12,6 +12,7 @@ Breaking changes * `AST::iter_functions` now returns an iterator instead of taking a closure. * `Module::get_script_function_by_signature` renamed to `Module::get_script_fn` and returns `&>`. * `Module::num_fn`, `Module::num_var` and `Module::num_iter` are removed and merged into `Module::count`. +* `Module::set_iter` is renamed to `Module::set_iter_raw`. * The `merge_namespaces` parameter to `Module::eval_ast_as_new` is removed and now defaults to `true`. * `GlobalFileModuleResolver` is removed because its performance gain over the `FileModuleResolver` is no longer very significant. * The following `EvalAltResult` variants are removed and merged into `EvalAltResult::ErrorMismatchDataType`: `ErrorCharMismatch`, `ErrorNumericIndexExpr`, `ErrorStringIndexExpr`, `ErrorImportExpr`, `ErrorLogicGuard`, `ErrorBooleanArgMismatch` @@ -32,6 +33,7 @@ New features * `Dynamic::from(&str)` now constructs a `Dynamic` with a copy of the string as value. * `AST::combine` and `AST::combine_filtered` allows combining two `AST`'s without creating a new one. * `map`, `filter`, `reduce`, `reduce_rev`, `some`, `all`, `extract`, `splice`, `chop` and `sort` functions for arrays. +* New `Module::set_iter`, `Module::set_iterable` and `Module::set_iterator` to define type iterators more easily. `Engine::register_iterator` is changed to use the simpler version. Enhancements ------------ diff --git a/src/api.rs b/src/api.rs index f07b05c0..14fb4083 100644 --- a/src/api.rs +++ b/src/api.rs @@ -3,7 +3,7 @@ use crate::any::{Dynamic, Variant}; use crate::engine::{Engine, EvalContext, Imports, State}; use crate::error::ParseError; -use crate::fn_native::{IteratorFn, SendSync}; +use crate::fn_native::SendSync; use crate::module::{FuncReturn, Module}; use crate::optimize::OptimizationLevel; use crate::parser::AST; @@ -174,11 +174,13 @@ impl Engine { self } - /// Register an iterator adapter for a type with the `Engine`. + /// Register an iterator adapter for an iterable type with the `Engine`. /// This is an advanced feature. #[inline(always)] - pub fn register_iterator(&mut self, f: IteratorFn) -> &mut Self { - self.global_module.set_iter(TypeId::of::(), f); + pub fn register_iterator, U: Variant + Clone>( + &mut self, + ) -> &mut Self { + self.global_module.set_iterable::(); self } diff --git a/src/engine.rs b/src/engine.rs index 127a4277..c282a436 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1782,21 +1782,22 @@ impl Engine { // For loop Stmt::For(x) => { 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(); + let iter_obj = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + let iter_type = iter_obj.type_id(); - if let Some(func) = self + let func = self .global_module - .get_iter(tid) - .or_else(|| self.packages.get_iter(tid)) - { + .get_iter(iter_type) + .or_else(|| self.packages.get_iter(iter_type)); + + if let Some(func) = func { // Add the loop variable let var_name = unsafe_cast_var_name_to_lifetime(name, &state); scope.push(var_name, ()); let index = scope.len() - 1; state.scope_level += 1; - for iter_value in func(iter_type) { + for iter_value in func(iter_obj) { let (loop_var, _) = scope.get_mut(index); let value = iter_value.flatten(); diff --git a/src/lib.rs b/src/lib.rs index b024ca69..dd277a73 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,7 +85,7 @@ mod utils; pub use any::Dynamic; pub use engine::{Engine, EvalContext}; pub use error::{ParseError, ParseErrorType}; -pub use fn_native::{FnPtr, IteratorFn}; +pub use fn_native::FnPtr; pub use fn_register::{RegisterFn, RegisterResultFn}; pub use module::Module; pub use parser::{ImmutableString, AST, INT}; diff --git a/src/module/mod.rs b/src/module/mod.rs index 566b678d..ed135f71 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1468,6 +1468,24 @@ impl Module { self } + /// Set a type iterator into the module. + pub fn set_iterable, U: Variant + Clone>( + &mut self, + ) -> &mut Self { + self.set_iter(TypeId::of::(), |obj: Dynamic| { + Box::new(obj.cast::().into_iter().map(Dynamic::from)) + }) + } + + /// Set an iterator type into the module as a type iterator. + pub fn set_iterator, U: Variant + Clone>( + &mut self, + ) -> &mut Self { + self.set_iter(TypeId::of::(), |obj: Dynamic| { + Box::new(obj.cast::().map(Dynamic::from)) + }) + } + /// Get the specified type iterator. pub(crate) fn get_iter(&self, id: TypeId) -> Option { self.type_iterators.get(&id).cloned() diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index d688ef3a..43543ad8 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -86,10 +86,7 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { combine_with_exported_module!(lib, "array", array_functions); // Register array iterator - lib.set_iter( - TypeId::of::(), - |arr| Box::new(arr.cast::().into_iter()) as Box>, - ); + lib.set_iterable::(); }); #[export_module] diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index 8914c6a5..eaf74fe0 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -1,23 +1,16 @@ -use crate::any::{Dynamic, Variant}; +use crate::any::Variant; use crate::def_package; use crate::module::{FuncReturn, Module}; use crate::parser::INT; -use crate::stdlib::{ - any::TypeId, - boxed::Box, - ops::{Add, Range}, -}; +use crate::stdlib::ops::{Add, Range}; // Register range function fn reg_range(lib: &mut Module) where Range: Iterator, { - lib.set_iter(TypeId::of::>(), |source| { - Box::new(source.cast::>().map(|x| x.into_dynamic())) - as Box> - }); + lib.set_iterator::, T>(); } fn get_range(from: T, to: T) -> FuncReturn> { @@ -55,10 +48,7 @@ where T: Variant + Clone + PartialOrd, StepRange: Iterator, { - lib.set_iter(TypeId::of::>(), |source| { - Box::new(source.cast::>().map(|x| x.into_dynamic())) - as Box> - }); + lib.set_iterator::, T>(); } fn get_step_range(from: T, to: T, step: T) -> FuncReturn> diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index c799e221..e3b78292 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -108,9 +108,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str // Register string iterator lib.set_iter( TypeId::of::(), - |arr| Box::new( - arr.cast::().chars().collect::>().into_iter().map(Into::into) - ) as Box>, + |s: Dynamic| Box::new(s.cast::().chars().collect::>().into_iter().map(Into::into)) ); }); From 3c9250b0bfd90bd9059e5ddc483a6b5d60aaac13 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 15 Oct 2020 13:28:22 +0800 Subject: [PATCH 09/17] Skip wrapping if function returns Dynamicc. --- codegen/src/function.rs | 102 +++++++++++++--------- codegen/src/rhai_module.rs | 9 +- codegen/src/test/function.rs | 67 +++++++++++--- codegen/ui_tests/return_mut_ref.stderr | 2 +- codegen/ui_tests/return_pointer.stderr | 2 +- codegen/ui_tests/return_shared_ref.stderr | 2 +- 6 files changed, 119 insertions(+), 65 deletions(-) diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 210e41df..0d34c266 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -16,7 +16,6 @@ use quote::{quote, quote_spanned}; use syn::{parse::Parse, parse::ParseStream, parse::Parser, spanned::Spanned}; use crate::attrs::{ExportInfo, ExportScope, ExportedParams}; -use crate::rhai_module::flatten_type_groups; #[derive(Clone, Debug, Eq, PartialEq)] pub enum Index { @@ -48,10 +47,10 @@ impl FnSpecialAccess { match self { FnSpecialAccess::None => None, FnSpecialAccess::Property(Property::Get(ref g)) => { - Some((format!("get${}", g.to_string()), g.to_string(), g.span())) + Some((format!("{}{}", FN_GET, g), g.to_string(), g.span())) } FnSpecialAccess::Property(Property::Set(ref s)) => { - Some((format!("set${}", s.to_string()), s.to_string(), s.span())) + Some((format!("{}{}", FN_SET, s), s.to_string(), s.span())) } FnSpecialAccess::Index(Index::Get) => Some(( FN_IDX_GET.to_string(), @@ -67,6 +66,14 @@ impl FnSpecialAccess { } } +pub(crate) fn flatten_type_groups(ty: &syn::Type) -> &syn::Type { + match ty { + syn::Type::Group(syn::TypeGroup { ref elem, .. }) + | syn::Type::Paren(syn::TypeParen { ref elem, .. }) => flatten_type_groups(elem.as_ref()), + _ => ty, + } +} + #[derive(Debug, Default)] pub(crate) struct ExportedFnParams { pub name: Option>, @@ -76,6 +83,8 @@ pub(crate) struct ExportedFnParams { pub special: FnSpecialAccess, } +pub const FN_GET: &str = "get$"; +pub const FN_SET: &str = "set$"; pub const FN_IDX_GET: &str = "index$get$"; pub const FN_IDX_SET: &str = "index$set$"; @@ -130,36 +139,24 @@ impl ExportedParams for ExportedFnParams { "use attribute 'index_set' instead", )) } - ("name", Some(s)) if s.value().starts_with("get$") => { + ("name", Some(s)) if s.value().starts_with(FN_GET) => { return Err(syn::Error::new( item_span, format!( "use attribute 'getter = \"{}\"' instead", - &s.value()["get$".len()..] + &s.value()[FN_GET.len()..] ), )) } - ("name", Some(s)) if s.value().starts_with("set$") => { + ("name", Some(s)) if s.value().starts_with(FN_SET) => { return Err(syn::Error::new( item_span, format!( "use attribute 'setter = \"{}\"' instead", - &s.value()["set$".len()..] + &s.value()[FN_SET.len()..] ), )) } - ("name", Some(s)) if s.value().contains('$') => { - return Err(syn::Error::new( - s.span(), - "Rhai function names may not contain dollar sign", - )) - } - ("name", Some(s)) if s.value().contains('.') => { - return Err(syn::Error::new( - s.span(), - "Rhai function names may not contain dot", - )) - } ("name", Some(s)) => name.push(s.value()), ("set", Some(s)) => { special = match special { @@ -225,6 +222,7 @@ pub(crate) struct ExportedFn { entire_span: proc_macro2::Span, signature: syn::Signature, is_public: bool, + return_dynamic: bool, mut_receiver: bool, params: ExportedFnParams, } @@ -235,6 +233,10 @@ impl Parse for ExportedFn { let entire_span = fn_all.span(); let str_type_path = syn::parse2::(quote! { str }).unwrap(); + let dynamic_type_path1 = syn::parse2::(quote! { Dynamic }).unwrap(); + let dynamic_type_path2 = syn::parse2::(quote! { rhai::Dynamic }).unwrap(); + let mut return_dynamic = false; + // #[cfg] attributes are not allowed on functions due to what is generated for them crate::attrs::deny_cfg_attr(&fn_all.attrs)?; @@ -250,11 +252,11 @@ impl Parse for ExportedFn { }) => true, syn::FnArg::Typed(syn::PatType { ref ty, .. }) => { match flatten_type_groups(ty.as_ref()) { - &syn::Type::Reference(syn::TypeReference { + syn::Type::Reference(syn::TypeReference { mutability: Some(_), .. }) => true, - &syn::Type::Reference(syn::TypeReference { + syn::Type::Reference(syn::TypeReference { mutability: None, ref elem, .. @@ -285,18 +287,18 @@ impl Parse for ExportedFn { _ => panic!("internal error: receiver argument outside of first position!?"), }; let is_ok = match flatten_type_groups(ty.as_ref()) { - &syn::Type::Reference(syn::TypeReference { + syn::Type::Reference(syn::TypeReference { mutability: Some(_), .. }) => false, - &syn::Type::Reference(syn::TypeReference { + syn::Type::Reference(syn::TypeReference { mutability: None, ref elem, .. }) => { matches!(flatten_type_groups(elem.as_ref()), &syn::Type::Path(ref p) if p.path == str_type_path) } - &syn::Type::Verbatim(_) => false, + syn::Type::Verbatim(_) => false, _ => true, }; if !is_ok { @@ -308,21 +310,26 @@ impl Parse for ExportedFn { } } - // No returning references or pointers. + // Check return type. if let syn::ReturnType::Type(_, ref rtype) = fn_all.sig.output { - match rtype.as_ref() { - &syn::Type::Ptr(_) => { + match flatten_type_groups(rtype.as_ref()) { + syn::Type::Ptr(_) => { return Err(syn::Error::new( fn_all.sig.output.span(), - "cannot return a pointer to Rhai", + "Rhai functions cannot return pointers", )) } - &syn::Type::Reference(_) => { + syn::Type::Reference(_) => { return Err(syn::Error::new( fn_all.sig.output.span(), - "cannot return a reference to Rhai", + "Rhai functions cannot return references", )) } + syn::Type::Path(p) + if p.path == dynamic_type_path1 || p.path == dynamic_type_path2 => + { + return_dynamic = true + } _ => {} } } @@ -330,6 +337,7 @@ impl Parse for ExportedFn { entire_span, signature: fn_all.sig, is_public, + return_dynamic, mut_receiver, params: ExportedFnParams::default(), }) @@ -419,7 +427,7 @@ impl ExportedFn { pub(crate) fn return_type(&self) -> Option<&syn::Type> { if let syn::ReturnType::Type(_, ref rtype) = self.signature.output { - Some(rtype) + Some(flatten_type_groups(rtype)) } else { None } @@ -437,7 +445,7 @@ impl ExportedFn { { return Err(syn::Error::new( self.signature.span(), - "return_raw functions must return Result", + "return_raw functions must return Result>", )); } @@ -467,7 +475,7 @@ impl ExportedFn { FnSpecialAccess::Property(Property::Set(_)) if self.return_type().is_some() => { return Err(syn::Error::new( self.signature.span(), - "property setter must return no value", + "property setter cannot return any value", )) } // 4a. Index getters must take the subject and the accessed "index" as arguments. @@ -495,7 +503,7 @@ impl ExportedFn { FnSpecialAccess::Index(Index::Set) if self.return_type().is_some() => { return Err(syn::Error::new( self.signature.span(), - "index setter must return no value", + "index setter cannot return a value", )) } _ => {} @@ -532,7 +540,7 @@ impl ExportedFn { dynamic_signature.ident = syn::Ident::new("dynamic_result_fn", proc_macro2::Span::call_site()); dynamic_signature.output = syn::parse2::(quote! { - -> Result + -> Result> }) .unwrap(); let arguments: Vec = dynamic_signature @@ -555,18 +563,22 @@ impl ExportedFn { .return_type() .map(|r| r.span()) .unwrap_or_else(|| proc_macro2::Span::call_site()); - if !self.params.return_raw { + if self.params.return_raw { quote_spanned! { return_span=> - type EvalBox = Box; pub #dynamic_signature { - Ok(Dynamic::from(super::#name(#(#arguments),*))) + super::#name(#(#arguments),*) + } + } + } else if self.return_dynamic { + quote_spanned! { return_span=> + pub #dynamic_signature { + Ok(super::#name(#(#arguments),*)) } } } else { quote_spanned! { return_span=> - type EvalBox = Box; pub #dynamic_signature { - super::#name(#(#arguments),*) + Ok(Dynamic::from(super::#name(#(#arguments),*))) } } } @@ -734,8 +746,14 @@ impl ExportedFn { .map(|r| r.span()) .unwrap_or_else(|| proc_macro2::Span::call_site()); let return_expr = if !self.params.return_raw { - quote_spanned! { return_span=> - Ok(Dynamic::from(#sig_name(#(#unpack_exprs),*))) + if self.return_dynamic { + quote_spanned! { return_span=> + Ok(#sig_name(#(#unpack_exprs),*)) + } + } else { + quote_spanned! { return_span=> + Ok(Dynamic::from(#sig_name(#(#unpack_exprs),*))) + } } } else { quote_spanned! { return_span=> diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index 94c0f720..f18e809e 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use quote::{quote, ToTokens}; use crate::attrs::ExportScope; +use crate::function::flatten_type_groups; use crate::function::{ExportedFn, FnSpecialAccess}; use crate::module::Module; @@ -174,14 +175,6 @@ pub(crate) fn generate_body( } } -pub(crate) fn flatten_type_groups(ty: &syn::Type) -> &syn::Type { - match ty { - syn::Type::Group(syn::TypeGroup { ref elem, .. }) - | syn::Type::Paren(syn::TypeParen { ref elem, .. }) => flatten_type_groups(elem.as_ref()), - _ => ty, - } -} - pub(crate) fn check_rename_collisions(fns: &Vec) -> Result<(), syn::Error> { fn make_key(name: impl ToString, itemfn: &ExportedFn) -> String { itemfn diff --git a/codegen/src/test/function.rs b/codegen/src/test/function.rs index f028c0b5..ea790f29 100644 --- a/codegen/src/test/function.rs +++ b/codegen/src/test/function.rs @@ -88,7 +88,10 @@ mod function_tests { }; let err = syn::parse2::(input_tokens).unwrap_err(); - assert_eq!(format!("{}", err), "cannot return a reference to Rhai"); + assert_eq!( + format!("{}", err), + "Rhai functions cannot return references" + ); } #[test] @@ -98,7 +101,7 @@ mod function_tests { }; let err = syn::parse2::(input_tokens).unwrap_err(); - assert_eq!(format!("{}", err), "cannot return a pointer to Rhai"); + assert_eq!(format!("{}", err), "Rhai functions cannot return pointers"); } #[test] @@ -295,8 +298,7 @@ mod generate_tests { pub fn token_input_types() -> Box<[TypeId]> { Token().input_types() } - type EvalBox = Box; - pub fn dynamic_result_fn() -> Result { + pub fn dynamic_result_fn() -> Result > { Ok(Dynamic::from(super::do_nothing())) } } @@ -340,8 +342,7 @@ mod generate_tests { pub fn token_input_types() -> Box<[TypeId]> { Token().input_types() } - type EvalBox = Box; - pub fn dynamic_result_fn(x: usize) -> Result { + pub fn dynamic_result_fn(x: usize) -> Result > { Ok(Dynamic::from(super::do_something(x))) } } @@ -351,6 +352,51 @@ mod generate_tests { assert_streams_eq(item_fn.generate(), expected_tokens); } + #[test] + fn return_dynamic() { + let input_tokens: TokenStream = quote! { + pub fn return_dynamic() -> (((rhai::Dynamic))) { + ().into() + } + }; + + let expected_tokens = quote! { + #[allow(unused)] + pub mod rhai_fn_return_dynamic { + use super::*; + struct Token(); + impl PluginFunction for Token { + fn call(&self, + args: &mut [&mut Dynamic] + ) -> Result> { + debug_assert_eq!(args.len(), 0usize, + "wrong arg count: {} != {}", args.len(), 0usize); + Ok(return_dynamic()) + } + + fn is_method_call(&self) -> bool { false } + fn is_variadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![].into_boxed_slice() + } + } + pub fn token_callable() -> CallableFunction { + Token().into() + } + pub fn token_input_types() -> Box<[TypeId]> { + Token().input_types() + } + pub fn dynamic_result_fn() -> Result > { + Ok(super::return_dynamic()) + } + } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_fn.generate(), expected_tokens); + } + #[test] fn one_arg_usize_fn_impl() { let input_tokens: TokenStream = quote! { @@ -417,8 +463,7 @@ mod generate_tests { pub fn token_input_types() -> Box<[TypeId]> { Token().input_types() } - type EvalBox = Box; - pub fn dynamic_result_fn(x: usize, y: usize) -> Result { + pub fn dynamic_result_fn(x: usize, y: usize) -> Result > { Ok(Dynamic::from(super::add_together(x, y))) } } @@ -464,8 +509,7 @@ mod generate_tests { pub fn token_input_types() -> Box<[TypeId]> { Token().input_types() } - type EvalBox = Box; - pub fn dynamic_result_fn(x: &mut usize, y: usize) -> Result { + pub fn dynamic_result_fn(x: &mut usize, y: usize) -> Result > { Ok(Dynamic::from(super::increment(x, y))) } } @@ -510,8 +554,7 @@ mod generate_tests { pub fn token_input_types() -> Box<[TypeId]> { Token().input_types() } - type EvalBox = Box; - pub fn dynamic_result_fn(message: &str) -> Result { + pub fn dynamic_result_fn(message: &str) -> Result > { Ok(Dynamic::from(super::special_print(message))) } } diff --git a/codegen/ui_tests/return_mut_ref.stderr b/codegen/ui_tests/return_mut_ref.stderr index c849a2c7..6e56c276 100644 --- a/codegen/ui_tests/return_mut_ref.stderr +++ b/codegen/ui_tests/return_mut_ref.stderr @@ -1,4 +1,4 @@ -error: cannot return a reference to Rhai +error: Rhai functions cannot return references --> $DIR/return_mut_ref.rs:12:38 | 12 | pub fn test_fn(input: &mut Clonable) -> &mut bool { diff --git a/codegen/ui_tests/return_pointer.stderr b/codegen/ui_tests/return_pointer.stderr index 1b736db6..9b771be6 100644 --- a/codegen/ui_tests/return_pointer.stderr +++ b/codegen/ui_tests/return_pointer.stderr @@ -1,4 +1,4 @@ -error: cannot return a pointer to Rhai +error: Rhai functions cannot return pointers --> $DIR/return_pointer.rs:12:33 | 12 | pub fn test_fn(input: Clonable) -> *const str { diff --git a/codegen/ui_tests/return_shared_ref.stderr b/codegen/ui_tests/return_shared_ref.stderr index 13577531..50d223ef 100644 --- a/codegen/ui_tests/return_shared_ref.stderr +++ b/codegen/ui_tests/return_shared_ref.stderr @@ -1,4 +1,4 @@ -error: cannot return a reference to Rhai +error: Rhai functions cannot return pointers --> $DIR/return_shared_ref.rs:12:33 | 12 | pub fn test_fn(input: Clonable) -> &'static str { From 8abb3c52037099cce5268714709a19d3e0c89ad2 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 15 Oct 2020 14:06:54 +0800 Subject: [PATCH 10/17] Fix ui tests. --- codegen/src/function.rs | 16 ++++++++-------- codegen/src/rhai_module.rs | 2 +- codegen/ui_tests/export_fn_raw_noreturn.stderr | 2 +- codegen/ui_tests/export_mod_raw_noreturn.stderr | 2 +- codegen/ui_tests/return_shared_ref.stderr | 2 +- codegen/ui_tests/rhai_fn_setter_return.stderr | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 0d34c266..a992e858 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -261,7 +261,7 @@ impl Parse for ExportedFn { ref elem, .. }) => match flatten_type_groups(elem.as_ref()) { - &syn::Type::Path(ref p) if p.path == str_type_path => false, + syn::Type::Path(ref p) if p.path == str_type_path => false, _ => { return Err(syn::Error::new( ty.span(), @@ -296,7 +296,7 @@ impl Parse for ExportedFn { ref elem, .. }) => { - matches!(flatten_type_groups(elem.as_ref()), &syn::Type::Path(ref p) if p.path == str_type_path) + matches!(flatten_type_groups(elem.as_ref()), syn::Type::Path(ref p) if p.path == str_type_path) } syn::Type::Verbatim(_) => false, _ => true, @@ -632,8 +632,8 @@ impl ExportedFn { let var = syn::Ident::new("arg0", proc_macro2::Span::call_site()); match first_arg { syn::FnArg::Typed(pattern) => { - let arg_type: &syn::Type = match flatten_type_groups(pattern.ty.as_ref()) { - &syn::Type::Reference(syn::TypeReference { ref elem, .. }) => elem.as_ref(), + let arg_type = match flatten_type_groups(pattern.ty.as_ref()) { + syn::Type::Reference(syn::TypeReference { ref elem, .. }) => elem.as_ref(), p => p, }; let downcast_span = quote_spanned!( @@ -670,14 +670,14 @@ impl ExportedFn { let is_ref; match arg { syn::FnArg::Typed(pattern) => { - let arg_type: &syn::Type = pattern.ty.as_ref(); + let arg_type = pattern.ty.as_ref(); let downcast_span = match flatten_type_groups(pattern.ty.as_ref()) { - &syn::Type::Reference(syn::TypeReference { + syn::Type::Reference(syn::TypeReference { mutability: None, ref elem, .. }) => match flatten_type_groups(elem.as_ref()) { - &syn::Type::Path(ref p) if p.path == str_type_path => { + syn::Type::Path(ref p) if p.path == str_type_path => { is_string = true; is_ref = true; quote_spanned!(arg_type.span()=> @@ -685,7 +685,7 @@ impl ExportedFn { } _ => panic!("internal error: why wasn't this found earlier!?"), }, - &syn::Type::Path(ref p) if p.path == string_type_path => { + syn::Type::Path(ref p) if p.path == string_type_path => { is_string = true; is_ref = false; quote_spanned!(arg_type.span()=> diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index f18e809e..fec67d1c 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -38,7 +38,7 @@ pub(crate) fn generate_body( if itemmod.skipped() { continue; } - let module_name: &syn::Ident = itemmod.module_name().unwrap(); + let module_name = itemmod.module_name().unwrap(); let exported_name: syn::LitStr = if let Some(name) = itemmod.exported_name() { syn::LitStr::new(&name, proc_macro2::Span::call_site()) } else { diff --git a/codegen/ui_tests/export_fn_raw_noreturn.stderr b/codegen/ui_tests/export_fn_raw_noreturn.stderr index 0687c8c6..db719f9b 100644 --- a/codegen/ui_tests/export_fn_raw_noreturn.stderr +++ b/codegen/ui_tests/export_fn_raw_noreturn.stderr @@ -1,4 +1,4 @@ -error: return_raw functions must return Result +error: return_raw functions must return Result> --> $DIR/export_fn_raw_noreturn.rs:10:5 | 10 | pub fn test_fn(input: &mut Point) { diff --git a/codegen/ui_tests/export_mod_raw_noreturn.stderr b/codegen/ui_tests/export_mod_raw_noreturn.stderr index 74fd381f..dc7bf10f 100644 --- a/codegen/ui_tests/export_mod_raw_noreturn.stderr +++ b/codegen/ui_tests/export_mod_raw_noreturn.stderr @@ -1,4 +1,4 @@ -error: return_raw functions must return Result +error: return_raw functions must return Result> --> $DIR/export_mod_raw_noreturn.rs:12:5 | 12 | pub fn test_fn(input: &mut Point) { diff --git a/codegen/ui_tests/return_shared_ref.stderr b/codegen/ui_tests/return_shared_ref.stderr index 50d223ef..9b7a91ca 100644 --- a/codegen/ui_tests/return_shared_ref.stderr +++ b/codegen/ui_tests/return_shared_ref.stderr @@ -1,4 +1,4 @@ -error: Rhai functions cannot return pointers +error: Rhai functions cannot return references --> $DIR/return_shared_ref.rs:12:33 | 12 | pub fn test_fn(input: Clonable) -> &'static str { diff --git a/codegen/ui_tests/rhai_fn_setter_return.stderr b/codegen/ui_tests/rhai_fn_setter_return.stderr index db37a80f..d55e2073 100644 --- a/codegen/ui_tests/rhai_fn_setter_return.stderr +++ b/codegen/ui_tests/rhai_fn_setter_return.stderr @@ -1,4 +1,4 @@ -error: property setter must return no value +error: property setter cannot return any value --> $DIR/rhai_fn_setter_return.rs:13:9 | 13 | pub fn test_fn(input: &mut Point, value: f32) -> bool { From a6fa94d946391d6e47a4f729971202b93208352d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 15 Oct 2020 14:36:10 +0800 Subject: [PATCH 11/17] Rhai function names with $ and . no longer fail. --- .../ui_tests/rhai_fn_rename_dollar_sign.rs | 28 ------------------- .../rhai_fn_rename_dollar_sign.stderr | 11 -------- codegen/ui_tests/rhai_fn_rename_dot.rs | 28 ------------------- codegen/ui_tests/rhai_fn_rename_dot.stderr | 11 -------- 4 files changed, 78 deletions(-) delete mode 100644 codegen/ui_tests/rhai_fn_rename_dollar_sign.rs delete mode 100644 codegen/ui_tests/rhai_fn_rename_dollar_sign.stderr delete mode 100644 codegen/ui_tests/rhai_fn_rename_dot.rs delete mode 100644 codegen/ui_tests/rhai_fn_rename_dot.stderr diff --git a/codegen/ui_tests/rhai_fn_rename_dollar_sign.rs b/codegen/ui_tests/rhai_fn_rename_dollar_sign.rs deleted file mode 100644 index 69af645c..00000000 --- a/codegen/ui_tests/rhai_fn_rename_dollar_sign.rs +++ /dev/null @@ -1,28 +0,0 @@ -use rhai::plugin::*; - -#[derive(Clone)] -pub struct Point { - x: f32, - y: f32, -} - -#[export_module] -pub mod test_module { - pub use super::Point; - #[rhai_fn(name = "big$caching")] - pub fn test_fn(input: Point) -> bool { - input.x > input.y - } -} - -fn main() { - let n = Point { - x: 0.0, - y: 10.0, - }; - if test_module::test_fn(n) { - println!("yes"); - } else { - println!("no"); - } -} diff --git a/codegen/ui_tests/rhai_fn_rename_dollar_sign.stderr b/codegen/ui_tests/rhai_fn_rename_dollar_sign.stderr deleted file mode 100644 index 3438bdfc..00000000 --- a/codegen/ui_tests/rhai_fn_rename_dollar_sign.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: Rhai function names may not contain dollar sign - --> $DIR/rhai_fn_rename_dollar_sign.rs:12:22 - | -12 | #[rhai_fn(name = "big$caching")] - | ^^^^^^^^^^^^^ - -error[E0433]: failed to resolve: use of undeclared crate or module `test_module` - --> $DIR/rhai_fn_rename_dollar_sign.rs:23:8 - | -23 | if test_module::test_fn(n) { - | ^^^^^^^^^^^ use of undeclared crate or module `test_module` diff --git a/codegen/ui_tests/rhai_fn_rename_dot.rs b/codegen/ui_tests/rhai_fn_rename_dot.rs deleted file mode 100644 index 9e2180d9..00000000 --- a/codegen/ui_tests/rhai_fn_rename_dot.rs +++ /dev/null @@ -1,28 +0,0 @@ -use rhai::plugin::*; - -#[derive(Clone)] -pub struct Point { - x: f32, - y: f32, -} - -#[export_module] -pub mod test_module { - pub use super::Point; - #[rhai_fn(name = "foo.bar")] - pub fn test_fn(input: Point) -> bool { - input.x > input.y - } -} - -fn main() { - let n = Point { - x: 0.0, - y: 10.0, - }; - if test_module::test_fn(n) { - println!("yes"); - } else { - println!("no"); - } -} diff --git a/codegen/ui_tests/rhai_fn_rename_dot.stderr b/codegen/ui_tests/rhai_fn_rename_dot.stderr deleted file mode 100644 index 964e1b34..00000000 --- a/codegen/ui_tests/rhai_fn_rename_dot.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: Rhai function names may not contain dot - --> $DIR/rhai_fn_rename_dot.rs:12:22 - | -12 | #[rhai_fn(name = "foo.bar")] - | ^^^^^^^^^ - -error[E0433]: failed to resolve: use of undeclared crate or module `test_module` - --> $DIR/rhai_fn_rename_dot.rs:23:8 - | -23 | if test_module::test_fn(n) { - | ^^^^^^^^^^^ use of undeclared crate or module `test_module` From ea9ef1091ab6c228692fad369922d5d3e3c11f92 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 15 Oct 2020 20:05:23 +0800 Subject: [PATCH 12/17] Add example to function pointer. --- doc/src/engine/custom-syntax.md | 3 +- doc/src/engine/var.md | 3 +- doc/src/language/fn-ptr.md | 61 +++++++++++++++++++++++++++++---- doc/src/rust/register-raw.md | 5 +-- 4 files changed, 62 insertions(+), 10 deletions(-) diff --git a/doc/src/engine/custom-syntax.md b/doc/src/engine/custom-syntax.md index f2a76db1..a0d38b9a 100644 --- a/doc/src/engine/custom-syntax.md +++ b/doc/src/engine/custom-syntax.md @@ -114,7 +114,8 @@ Any custom syntax must include an _implementation_ of it. The function signature of an implementation is: -> `Fn(scope: &mut Scope, context: &mut EvalContext, inputs: &[Expression]) -> Result>` +> `Fn(scope: &mut Scope, context: &mut EvalContext, inputs: &[Expression])` +> `-> Result>` where: diff --git a/doc/src/engine/var.md b/doc/src/engine/var.md index a6cc0cf3..f1119117 100644 --- a/doc/src/engine/var.md +++ b/doc/src/engine/var.md @@ -62,7 +62,8 @@ Function Signature The function signature passed to `Engine::on_var` takes the following form: -> `Fn(name: &str, index: usize, scope: &Scope, context: &EvalContext) -> Result, Box> + 'static` +> `Fn(name: &str, index: usize, scope: &Scope, context: &EvalContext)` +> `-> Result, Box> + 'static` where: diff --git a/doc/src/language/fn-ptr.md b/doc/src/language/fn-ptr.md index e377e7e6..b8412138 100644 --- a/doc/src/language/fn-ptr.md +++ b/doc/src/language/fn-ptr.md @@ -13,7 +13,7 @@ Built-in methods ---------------- The following standard methods (mostly defined in the [`BasicFnPackage`][packages] but excluded if -using a [raw `Engine`]) operate on [strings]: +using a [raw `Engine`]) operate on function pointers: | Function | Parameter(s) | Description | | -------------------------- | ------------ | ---------------------------------------------------------------------------- | @@ -64,7 +64,7 @@ Global Namespace Only Because of their dynamic nature, function pointers cannot refer to functions in [`import`]-ed [modules]. They can only refer to functions within the global [namespace][function namespace]. -See [function namespaces] for more details. +See _[Function Namespaces]_ for more details. ```rust import "foo" as f; // assume there is 'f::do_work()' @@ -135,13 +135,13 @@ map[func].call(42); ``` -Binding the `this` Pointer -------------------------- +Bind the `this` Pointer +---------------------- -When `call` is called as a _method_ but not on a `FnPtr` value, it is possible to dynamically dispatch +When `call` is called as a _method_ but not on a function pointer, it is possible to dynamically dispatch to a function call while binding the object in the method call to the `this` pointer of the function. -To achieve this, pass the `FnPtr` value as the _first_ argument to `call`: +To achieve this, pass the function pointer as the _first_ argument to `call`: ```rust fn add(x) { // define function which uses 'this' @@ -167,3 +167,52 @@ Beware that this only works for _method-call_ style. Normal function-call style the `this` pointer (for syntactic reasons). Therefore, obviously, binding the `this` pointer is unsupported under [`no_object`]. + + +Call a Function Pointer in Rust +------------------------------ + +It is completely normal to register a Rust function with an [`Engine`] that takes parameters +whose types are function pointers. The Rust type in question is `rhai::FnPtr`. + +A function pointer in Rhai is essentially syntactic sugar wrapping the _name_ of a function +to call in script. Therefore, the script's [`AST`] is required to call a function pointer, +as well as the entire _execution context_ that the script is running in. + +For a rust function taking a function pointer as parameter, the [Low-Level API](../rust/register-raw.md) +must be used to register the function. + +Essentially, use the low-level `Engine::register_raw_fn` method to register the function. +`FnPtr::call_dynamic` is used to actually call the function pointer, passing to it the +current scripting [`Engine`], collection of script-defined functions, the `this` pointer, +and other necessary arguments. + +```rust +use rhai::{Engine, Module, Dynamic, FnPtr}; + +let mut engine = Engine::new(); + +// Define Rust function in required low-level API signature +fn call_fn_ptr_with_value(engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]) + -> Result> +{ + // 'args' is guaranteed to contain enough arguments of the correct types + let fp = std::mem::take(args[1]).cast::(); // 2nd argument - function pointer + let value = args[2].clone(); // 3rd argument - function argument + let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer + + // Use 'FnPtr::call_dynamic' to call the function pointer. + // Beware, private script-defined functions will not be found. + fp.call_dynamic(engine, lib, Some(this_ptr), [value]) +} + +// Register a Rust function using the low-level API +engine.register_raw_fn("super_call", + &[ // parameter types + std::any::TypeId::of::(), + std::any::TypeId::of::(), + std::any::TypeId::of::() + ], + call_fn_ptr_with_value +); +``` diff --git a/doc/src/rust/register-raw.md b/doc/src/rust/register-raw.md index 2231320e..460df172 100644 --- a/doc/src/rust/register-raw.md +++ b/doc/src/rust/register-raw.md @@ -59,7 +59,8 @@ Function Signature The function signature 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: @@ -117,7 +118,7 @@ engine.register_raw_fn( std::any::TypeId::of::(), std::any::TypeId::of::(), ], - move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { + |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { // 'args' is guaranteed to contain enough arguments of the correct types let fp = std::mem::take(args[1]).cast::(); // 2nd argument - function pointer From 3b99b8f1666bc32120af6660f26dff936ede70ab Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 15 Oct 2020 22:11:18 +0800 Subject: [PATCH 13/17] Simplify iterator registration. --- src/api.rs | 10 ++++++---- src/module/mod.rs | 16 ++++++++++------ src/packages/array_basic.rs | 2 +- src/packages/iter_basic.rs | 28 ++++++---------------------- src/packages/map_basic.rs | 7 ++++--- 5 files changed, 27 insertions(+), 36 deletions(-) diff --git a/src/api.rs b/src/api.rs index 14fb4083..8018e447 100644 --- a/src/api.rs +++ b/src/api.rs @@ -177,10 +177,12 @@ impl Engine { /// Register an iterator adapter for an iterable type with the `Engine`. /// This is an advanced feature. #[inline(always)] - pub fn register_iterator, U: Variant + Clone>( - &mut self, - ) -> &mut Self { - self.global_module.set_iterable::(); + pub fn register_iterator(&mut self) -> &mut Self + where + T: Variant + Clone + Iterator, + ::Item: Variant + Clone, + { + self.global_module.set_iterable::(); self } diff --git a/src/module/mod.rs b/src/module/mod.rs index ed135f71..d7cc40d9 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1469,18 +1469,22 @@ impl Module { } /// Set a type iterator into the module. - pub fn set_iterable, U: Variant + Clone>( - &mut self, - ) -> &mut Self { + pub fn set_iterable(&mut self) -> &mut Self + where + T: Variant + Clone + IntoIterator, + ::Item: Variant + Clone, + { self.set_iter(TypeId::of::(), |obj: Dynamic| { Box::new(obj.cast::().into_iter().map(Dynamic::from)) }) } /// Set an iterator type into the module as a type iterator. - pub fn set_iterator, U: Variant + Clone>( - &mut self, - ) -> &mut Self { + pub fn set_iterator(&mut self) -> &mut Self + where + T: Variant + Clone + Iterator, + ::Item: Variant + Clone, + { self.set_iter(TypeId::of::(), |obj: Dynamic| { Box::new(obj.cast::().map(Dynamic::from)) }) diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 43543ad8..de5bdfcc 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -86,7 +86,7 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { combine_with_exported_module!(lib, "array", array_functions); // Register array iterator - lib.set_iterable::(); + lib.set_iterable::(); }); #[export_module] diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index eaf74fe0..c4832c95 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -1,18 +1,10 @@ use crate::any::Variant; use crate::def_package; -use crate::module::{FuncReturn, Module}; +use crate::module::FuncReturn; use crate::parser::INT; use crate::stdlib::ops::{Add, Range}; -// Register range function -fn reg_range(lib: &mut Module) -where - Range: Iterator, -{ - lib.set_iterator::, T>(); -} - fn get_range(from: T, to: T) -> FuncReturn> { Ok(from..to) } @@ -42,15 +34,6 @@ where } } -fn reg_step(lib: &mut Module) -where - for<'a> &'a T: Add<&'a T, Output = T>, - T: Variant + Clone + PartialOrd, - StepRange: Iterator, -{ - lib.set_iterator::, T>(); -} - fn get_step_range(from: T, to: T, step: T) -> FuncReturn> where for<'a> &'a T: Add<&'a T, Output = T>, @@ -60,14 +43,15 @@ where } def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { - reg_range::(lib); + lib.set_iterator::>(); + lib.set_fn_2("range", get_range::); if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { macro_rules! reg_range { ($lib:expr, $x:expr, $( $y:ty ),*) => ( $( - reg_range::<$y>($lib); + $lib.set_iterator::>(); $lib.set_fn_2($x, get_range::<$y>); )* ) @@ -80,14 +64,14 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { } } - reg_step::(lib); + lib.set_iterator::>(); lib.set_fn_3("range", get_step_range::); if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { macro_rules! reg_step { ($lib:expr, $x:expr, $( $y:ty ),*) => ( $( - reg_step::<$y>($lib); + $lib.set_iterator::>(); $lib.set_fn_3($x, get_step_range::<$y>); )* ) diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 099f546c..26fc3fe3 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -6,7 +6,8 @@ use crate::engine::Map; use crate::parser::{ImmutableString, INT}; use crate::plugin::*; -use crate::stdlib::vec::Vec; +#[cfg(not(feature = "no_index"))] +use crate::engine::Array; def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { combine_with_exported_module!(lib, "map", map_functions); @@ -47,10 +48,10 @@ mod map_functions { #[cfg(not(feature = "no_index"))] pub mod indexing { - pub fn keys(map: &mut Map) -> Vec { + pub fn keys(map: &mut Map) -> Array { map.iter().map(|(k, _)| k.clone().into()).collect() } - pub fn values(map: &mut Map) -> Vec { + pub fn values(map: &mut Map) -> Array { map.iter().map(|(_, v)| v.clone()).collect() } } From fb05e811b7aa77e34f0bda9194efce159aef22b7 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 15 Oct 2020 22:11:40 +0800 Subject: [PATCH 14/17] Map::keys and Map::values can be used method-call style. --- doc/src/language/for.md | 6 +++--- doc/src/language/object-maps.md | 31 +++++++++++++++++++++++++++++-- src/stdlib.rs | 2 +- tests/for.rs | 4 ++-- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/doc/src/language/for.md b/doc/src/language/for.md index c60372a8..8fa560d2 100644 --- a/doc/src/language/for.md +++ b/doc/src/language/for.md @@ -79,7 +79,7 @@ for x in range(0, 50, 3) { // step by 3 Iterate Through Object Maps -------------------------- -Two functions, `keys` and `values`, return [arrays] containing cloned _copies_ +Two methods, `keys` and `values`, return [arrays] containing cloned _copies_ of all property names and values of an [object map], respectively. These [arrays] can be iterated. @@ -88,7 +88,7 @@ These [arrays] can be iterated. let map = #{a:1, b:3, c:5, d:7, e:9}; // Property names are returned in unsorted, random order -for x in keys(map) { +for x in map.keys() { if x > 10 { continue; } // skip to the next iteration print(x); @@ -97,7 +97,7 @@ for x in keys(map) { } // Property values are returned in unsorted, random order -for val in values(map) { +for val in map.values() { print(val); } ``` diff --git a/doc/src/language/object-maps.md b/doc/src/language/object-maps.md index 937b1818..7c7fba24 100644 --- a/doc/src/language/object-maps.md +++ b/doc/src/language/object-maps.md @@ -126,11 +126,11 @@ y.remove("a") == 1; // remove property y.len() == 2; y.has("a") == false; -for name in keys(y) { // get an array of all the property names via the 'keys' function +for name in y.keys() { // get an array of all the property names via 'keys' print(name); } -for val in values(y) { // get an array of all the property values via the 'values' function +for val in y.values() { // get an array of all the property values via 'values' print(val); } @@ -138,3 +138,30 @@ y.clear(); // empty the object map y.len() == 0; ``` + + +No Support for Property Getters +------------------------------ + +In order not to affect the speed of accessing properties in an object map, new property +[getters][getters/setters] cannot be registered because they conflict with the syntax of +property access. + +A property [getter][getters/setters] function registered via `Engine::register_get`, for example, +for a `Map` will never be found - instead, the property will be looked up in the object map. + +Therefore, _method-call_ notation must be used for built-in properties: + +```rust +map.len // access property 'len', returns '()' if not found + +map.len() // returns the number of properties + +map.keys // access property 'keys', returns '()' if not found + +map.keys() // returns array of all property names + +map.values // access property 'values', returns '()' if not found + +map.values() // returns array of all property values +``` diff --git a/src/stdlib.rs b/src/stdlib.rs index 5ec6c2a0..165b9068 100644 --- a/src/stdlib.rs +++ b/src/stdlib.rs @@ -16,7 +16,7 @@ mod inner { pub use core_error as error; pub mod collections { - pub use hashbrown::{HashMap, HashSet}; + pub use hashbrown::{hash_map, hash_set, HashMap, HashSet}; } } diff --git a/tests/for.rs b/tests/for.rs index e3b561d6..0de226f0 100644 --- a/tests/for.rs +++ b/tests/for.rs @@ -61,10 +61,10 @@ fn test_for_object() -> Result<(), Box> { let keys = ""; let map = #{a: 1, b: 2, c: 3}; - for key in keys(map) { + for key in map.keys() { keys += key; } - for value in values(map) { + for value in map.values() { sum += value; } From 1e21a7f7e7147f8848381b45b1f8edf213d2c318 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 15 Oct 2020 23:30:30 +0800 Subject: [PATCH 15/17] Introduce IndexChainValue. --- src/engine.rs | 122 +++++++++++++++++++++++++++++++++++++------------ src/fn_call.rs | 112 ++++++++++++++++++++++++--------------------- 2 files changed, 151 insertions(+), 83 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index c282a436..b8bbbc1e 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -129,6 +129,54 @@ pub enum ChainType { Dot, } +#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] +#[derive(Debug, Clone)] +pub enum IndexChainValue { + None, + FnCallArgs(StaticVec), + Value(Dynamic), +} + +#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] +impl IndexChainValue { + /// Return the `Dynamic` value. + /// + /// # Panics + /// + /// Panics if not `IndexChainValue::Value`. + pub fn as_value(self) -> Dynamic { + match self { + Self::None | Self::FnCallArgs(_) => panic!("expecting IndexChainValue::Value"), + Self::Value(value) => value, + } + } + /// Return the `StaticVec` value. + /// + /// # Panics + /// + /// Panics if not `IndexChainValue::FnCallArgs`. + pub fn as_fn_call_args(self) -> StaticVec { + match self { + Self::None | Self::Value(_) => panic!("expecting IndexChainValue::FnCallArgs"), + Self::FnCallArgs(value) => value, + } + } +} + +#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] +impl From> for IndexChainValue { + fn from(value: StaticVec) -> Self { + Self::FnCallArgs(value) + } +} + +#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] +impl From for IndexChainValue { + fn from(value: Dynamic) -> Self { + Self::Value(value) + } +} + /// A type that encapsulates a mutation target for an expression with side effects. #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[derive(Debug)] @@ -790,7 +838,7 @@ impl Engine { this_ptr: &mut Option<&mut Dynamic>, target: &mut Target, rhs: &Expr, - idx_values: &mut StaticVec, + mut idx_values: StaticVec, chain_type: ChainType, level: usize, new_val: Option<(Dynamic, Position)>, @@ -820,6 +868,7 @@ impl Engine { Expr::Dot(x) | Expr::Index(x) => { let (idx, expr, pos) = x.as_ref(); let idx_pos = idx.position(); + let idx_val = idx_val.as_value(); let obj_ptr = &mut self.get_indexed_mut( state, lib, target, idx_val, idx_pos, false, true, level, )?; @@ -832,6 +881,7 @@ impl Engine { } // xxx[rhs] = new_val _ if new_val.is_some() => { + let idx_val = idx_val.as_value(); let mut idx_val2 = idx_val.clone(); // `call_setter` is introduced to bypass double mutable borrowing of target @@ -876,9 +926,11 @@ impl Engine { Ok(Default::default()) } // xxx[rhs] - _ => self - .get_indexed_mut(state, lib, target, idx_val, pos, false, true, level) - .map(|v| (v.take_or_clone(), false)), + _ => { + let idx_val = idx_val.as_value(); + self.get_indexed_mut(state, lib, target, idx_val, pos, false, true, level) + .map(|v| (v.take_or_clone(), false)) + } } } @@ -889,9 +941,9 @@ impl Engine { Expr::FnCall(x) if x.1.is_none() => { let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref(); let def_val = def_val.map(Into::::into); + let args = idx_val.as_fn_call_args(); self.make_method_call( - state, lib, name, *hash, target, idx_val, &def_val, *native, false, - level, + state, lib, name, *hash, target, args, &def_val, *native, false, level, ) .map_err(|err| err.fill_position(*pos)) } @@ -956,10 +1008,11 @@ impl Engine { Expr::FnCall(x) if x.1.is_none() => { let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref(); let def_val = def_val.map(Into::::into); + let args = idx_val.as_fn_call_args(); let (val, _) = self .make_method_call( - state, lib, name, *hash, target, idx_val, &def_val, - *native, false, level, + state, lib, name, *hash, target, args, &def_val, *native, + false, level, ) .map_err(|err| err.fill_position(*pos))?; val.into() @@ -1034,10 +1087,11 @@ impl Engine { Expr::FnCall(x) if x.1.is_none() => { let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref(); let def_val = def_val.map(Into::::into); + let args = idx_val.as_fn_call_args(); let (mut val, _) = self .make_method_call( - state, lib, name, *hash, target, idx_val, &def_val, - *native, false, level, + state, lib, name, *hash, target, args, &def_val, *native, + false, level, ) .map_err(|err| err.fill_position(*pos))?; let val = &mut val; @@ -1083,10 +1137,19 @@ impl Engine { _ => unreachable!(), }; - let idx_values = &mut StaticVec::new(); + let mut idx_values = StaticVec::new(); self.eval_indexed_chain( - scope, mods, state, lib, this_ptr, dot_rhs, chain_type, idx_values, 0, level, + scope, + mods, + state, + lib, + this_ptr, + dot_rhs, + chain_type, + &mut idx_values, + 0, + level, )?; match dot_lhs { @@ -1133,11 +1196,8 @@ impl Engine { } } - /// Evaluate a chain of indexes and store the results in a list. - /// The first few results are stored in the array `list` which is of fixed length. - /// Any spill-overs are stored in `more`, which is dynamic. - /// The fixed length array is used to avoid an allocation in the overwhelming cases of just a few levels of indexing. - /// The total number of values is returned. + /// Evaluate a chain of indexes and store the results in a StaticVec. + /// StaticVec is used to avoid an allocation in the overwhelming cases of just a few levels of indexing. #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] fn eval_indexed_chain( &self, @@ -1148,7 +1208,7 @@ impl Engine { this_ptr: &mut Option<&mut Dynamic>, expr: &Expr, chain_type: ChainType, - idx_values: &mut StaticVec, + idx_values: &mut StaticVec, size: usize, level: usize, ) -> Result<(), Box> { @@ -1162,31 +1222,30 @@ impl Engine { .map(|arg_expr| { self.eval_expr(scope, mods, state, lib, this_ptr, arg_expr, level) }) - .collect::, _>>()?; + .collect::, _>>()?; - idx_values.push(Dynamic::from(arg_values)); + idx_values.push(arg_values.into()); } Expr::FnCall(_) => unreachable!(), - Expr::Property(_) => idx_values.push(().into()), // Store a placeholder - no need to copy the property name + Expr::Property(_) => idx_values.push(IndexChainValue::None), Expr::Index(x) | Expr::Dot(x) => { let (lhs, rhs, _) = x.as_ref(); // Evaluate in left-to-right order let lhs_val = match lhs { - Expr::Property(_) => Default::default(), // Store a placeholder in case of a property + Expr::Property(_) => IndexChainValue::None, Expr::FnCall(x) if chain_type == ChainType::Dot && x.1.is_none() => { - let arg_values = x - .3 - .iter() + x.3.iter() .map(|arg_expr| { self.eval_expr(scope, mods, state, lib, this_ptr, arg_expr, level) }) - .collect::, _>>()?; - - Dynamic::from(arg_values) + .collect::, _>>()? + .into() } Expr::FnCall(_) => unreachable!(), - _ => self.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?, + _ => self + .eval_expr(scope, mods, state, lib, this_ptr, lhs, level)? + .into(), }; // Push in reverse order @@ -1201,7 +1260,10 @@ impl Engine { idx_values.push(lhs_val); } - _ => idx_values.push(self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?), + _ => idx_values.push( + self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? + .into(), + ), } Ok(()) diff --git a/src/fn_call.rs b/src/fn_call.rs index 62c2ca28..317930e3 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -695,7 +695,7 @@ impl Engine { name: &str, hash_script: u64, target: &mut Target, - idx_val: Dynamic, + mut call_args: StaticVec, def_val: &Option, native: bool, pub_only: bool, @@ -705,7 +705,6 @@ impl Engine { // Get a reference to the mutation target Dynamic let obj = target.as_mut(); - let mut idx = idx_val.cast::>(); let mut _fn_name = name; let (result, updated) = if _fn_name == KEYWORD_FN_PTR_CALL && obj.is::() { @@ -718,12 +717,12 @@ impl Engine { let hash = if native { 0 } else { - calc_fn_hash(empty(), fn_name, curry.len() + idx.len(), empty()) + calc_fn_hash(empty(), fn_name, curry.len() + call_args.len(), empty()) }; // Arguments are passed as-is, adding the curried arguments let mut arg_values = curry .iter_mut() - .chain(idx.iter_mut()) + .chain(call_args.iter_mut()) .collect::>(); let args = arg_values.as_mut(); @@ -731,9 +730,12 @@ impl Engine { self.exec_fn_call( 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::() { + } else if _fn_name == KEYWORD_FN_PTR_CALL + && call_args.len() > 0 + && call_args[0].is::() + { // FnPtr call on object - let fn_ptr = idx.remove(0).cast::(); + let fn_ptr = call_args.remove(0).cast::(); let mut curry = fn_ptr.curry().iter().cloned().collect::>(); // Redirect function name let fn_name = fn_ptr.get_fn_name().clone(); @@ -741,12 +743,12 @@ impl Engine { let hash = if native { 0 } else { - calc_fn_hash(empty(), &fn_name, curry.len() + idx.len(), empty()) + calc_fn_hash(empty(), &fn_name, curry.len() + call_args.len(), empty()) }; // Replace the first argument with the object pointer, adding the curried arguments let mut arg_values = once(obj) .chain(curry.iter_mut()) - .chain(idx.iter_mut()) + .chain(call_args.iter_mut()) .collect::>(); let args = arg_values.as_mut(); @@ -764,7 +766,7 @@ impl Engine { .curry() .iter() .cloned() - .chain(idx.into_iter()) + .chain(call_args.into_iter()) .collect(), ) .into(), @@ -773,7 +775,7 @@ impl Engine { } else if { #[cfg(not(feature = "no_closure"))] { - _fn_name == KEYWORD_IS_SHARED && idx.is_empty() + _fn_name == KEYWORD_IS_SHARED && call_args.is_empty() } #[cfg(feature = "no_closure")] false @@ -799,13 +801,13 @@ impl Engine { .iter() .cloned() .enumerate() - .for_each(|(i, v)| idx.insert(i, v)); + .for_each(|(i, v)| call_args.insert(i, v)); } // Recalculate the hash based on the new function name and new arguments hash = if native { 0 } else { - calc_fn_hash(empty(), _fn_name, idx.len(), empty()) + calc_fn_hash(empty(), _fn_name, call_args.len(), empty()) }; } } @@ -816,7 +818,9 @@ impl Engine { } // Attached object pointer in front of the arguments - let mut arg_values = once(obj).chain(idx.iter_mut()).collect::>(); + let mut arg_values = once(obj) + .chain(call_args.iter_mut()) + .collect::>(); let args = arg_values.as_mut(); self.exec_fn_call( @@ -873,28 +877,29 @@ impl Engine { // Handle curry() if name == KEYWORD_FN_PTR_CURRY && args_expr.len() > 1 { let expr = args_expr.get(0).unwrap(); - let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; - if !arg_value.is::() { + if !fn_ptr.is::() { return Err(self.make_type_mismatch_err::( - self.map_type_name(arg_value.type_name()), + self.map_type_name(fn_ptr.type_name()), expr.position(), )); } - let (fn_name, fn_curry) = arg_value.cast::().take_data(); + let (fn_name, fn_curry) = fn_ptr.cast::().take_data(); - let curry: StaticVec<_> = args_expr + let mut curry = fn_curry.clone(); + + // Append the new curried arguments to the existing list. + args_expr .iter() .skip(1) - .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) - .collect::>()?; + .try_for_each(|expr| -> Result<(), Box> { + curry.push(self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?); + Ok(()) + })?; - return Ok(FnPtr::new_unchecked( - fn_name, - fn_curry.into_iter().chain(curry.into_iter()).collect(), - ) - .into()); + return Ok(FnPtr::new_unchecked(fn_name, curry).into()); } // Handle is_shared() @@ -917,24 +922,27 @@ impl Engine { && !self.has_override(lib, 0, hash_script, pub_only) { let expr = args_expr.get(0).unwrap(); - let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; - if arg_value.is::() { - let fn_ptr = arg_value.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 { + if !fn_ptr.is::() { return Err(self.make_type_mismatch_err::( - self.map_type_name(arg_value.type_name()), + self.map_type_name(fn_ptr.type_name()), expr.position(), )); } + + let fn_ptr = fn_ptr.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()); } // Handle is_def_var() @@ -943,8 +951,8 @@ impl Engine { if !self.has_override(lib, hash_fn, hash_script, pub_only) { let expr = args_expr.get(0).unwrap(); - let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; - let var_name = arg_value.as_str().map_err(|err| { + let var_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + let var_name = var_name.as_str().map_err(|err| { self.make_type_mismatch_err::(err, expr.position()) })?; if var_name.is_empty() { @@ -967,20 +975,18 @@ impl Engine { ); if !self.has_override(lib, hash_fn, hash_script, pub_only) { - let fn_name_expr = args_expr.get(0).unwrap(); - let num_params_expr = args_expr.get(1).unwrap(); + let expr0 = args_expr.get(0).unwrap(); + let expr1 = args_expr.get(1).unwrap(); - let arg0_value = - self.eval_expr(scope, mods, state, lib, this_ptr, fn_name_expr, level)?; - let arg1_value = - self.eval_expr(scope, mods, state, lib, this_ptr, num_params_expr, level)?; + let fn_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr0, level)?; + let num_params = self.eval_expr(scope, mods, state, lib, this_ptr, expr1, level)?; - let fn_name = arg0_value.as_str().map_err(|err| { - self.make_type_mismatch_err::(err, fn_name_expr.position()) - })?; - let num_params = arg1_value.as_int().map_err(|err| { - self.make_type_mismatch_err::(err, num_params_expr.position()) + let fn_name = fn_name.as_str().map_err(|err| { + self.make_type_mismatch_err::(err, expr0.position()) })?; + let num_params = num_params + .as_int() + .map_err(|err| self.make_type_mismatch_err::(err, expr1.position()))?; if fn_name.is_empty() || num_params < 0 { return Ok(false.into()); @@ -999,8 +1005,8 @@ impl Engine { // eval - only in function call style let prev_len = scope.len(); let expr = args_expr.get(0).unwrap(); - let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; - let script = arg_value.as_str().map_err(|typ| { + let script = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + let script = script.as_str().map_err(|typ| { self.make_type_mismatch_err::(typ, expr.position()) })?; let result = if !script.is_empty() { From 54d5b293908cf73fadf398a6411ebb11d53c358d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 15 Oct 2020 23:44:05 +0800 Subject: [PATCH 16/17] Remove clone. --- src/fn_call.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/fn_call.rs b/src/fn_call.rs index 317930e3..915985d4 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -886,20 +886,18 @@ impl Engine { )); } - let (fn_name, fn_curry) = fn_ptr.cast::().take_data(); - - let mut curry = fn_curry.clone(); + let (fn_name, mut fn_curry) = fn_ptr.cast::().take_data(); // Append the new curried arguments to the existing list. args_expr .iter() .skip(1) .try_for_each(|expr| -> Result<(), Box> { - curry.push(self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?); + fn_curry.push(self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?); Ok(()) })?; - return Ok(FnPtr::new_unchecked(fn_name, curry).into()); + return Ok(FnPtr::new_unchecked(fn_name, fn_curry).into()); } // Handle is_shared() From 849aec06212c1de1a98fd57551e2e9444c5a3c05 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 16 Oct 2020 17:32:26 +0800 Subject: [PATCH 17/17] Set version to 0.19.1. --- Cargo.toml | 2 +- RELEASES.md | 7 ++++--- doc/src/context.json | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index aafb2ac3..7437b53e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ members = [ [package] name = "rhai" -version = "0.20.0" +version = "0.19.1" edition = "2018" authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"] description = "Embedded scripting for Rust" diff --git a/RELEASES.md b/RELEASES.md index 4ce09906..63c761f4 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,10 +1,11 @@ Rhai Release Notes ================== -Version 0.20.0 +Version 0.19.1 ============== -This version adds a variable resolver with the ability to short-circuit variable access. +This version adds a variable resolver with the ability to short-circuit variable access, +plus a whole bunch of array methods. Breaking changes ---------------- @@ -33,7 +34,7 @@ New features * `Dynamic::from(&str)` now constructs a `Dynamic` with a copy of the string as value. * `AST::combine` and `AST::combine_filtered` allows combining two `AST`'s without creating a new one. * `map`, `filter`, `reduce`, `reduce_rev`, `some`, `all`, `extract`, `splice`, `chop` and `sort` functions for arrays. -* New `Module::set_iter`, `Module::set_iterable` and `Module::set_iterator` to define type iterators more easily. `Engine::register_iterator` is changed to use the simpler version. +* New `Module::set_iterable` and `Module::set_iterator` to define type iterators more easily. `Engine::register_iterator` is changed to use the simpler version. Enhancements ------------ diff --git a/doc/src/context.json b/doc/src/context.json index 79f961ec..d451bd41 100644 --- a/doc/src/context.json +++ b/doc/src/context.json @@ -1,5 +1,5 @@ { - "version": "0.20.0", + "version": "0.19.1", "repoHome": "https://github.com/jonathandturner/rhai/blob/master", "repoTree": "https://github.com/jonathandturner/rhai/tree/master", "rootUrl": "",