From d91d983c742c922fa39f09c4679c6886267444fa Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 23 Sep 2020 22:48:28 +0800 Subject: [PATCH 1/6] Enable dots on numbers to parse as method calls. --- src/token.rs | 33 +++++++++++++++++++++++++++++++-- tests/number_literals.rs | 12 +++++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/token.rs b/src/token.rs index ad0ef340..6245310a 100644 --- a/src/token.rs +++ b/src/token.rs @@ -731,6 +731,7 @@ pub struct TokenizeState { /// /// This trait is volatile and may change. pub trait InputStream { + fn unread(&mut self, ch: char); /// Get the next character fn get_next(&mut self) -> Option; /// Peek the next character @@ -1014,8 +1015,21 @@ fn get_next_token_inner( } #[cfg(not(feature = "no_float"))] '.' => { - result.push(next_char); - eat_next(stream, pos); + stream.get_next().unwrap(); + + // Check if followed by digits (or _) + match stream.peek_next().unwrap_or('\0') { + '0'..='9' | '_' => { + result.push(next_char); + pos.advance() + } + _ => { + // Not a floating-point number + stream.unread(next_char); + break; + } + } + while let Some(next_char_in_float) = stream.peek_next() { match next_char_in_float { '0'..='9' | '_' => { @@ -1499,6 +1513,8 @@ fn is_id_continue(x: char) -> bool { /// A type that implements the `InputStream` trait. /// Multiple character streams are jointed together to form one single stream. pub struct MultiInputsStream<'a> { + /// Buffered character, if any. + buf: Option, /// The input character streams. streams: StaticVec>>, /// The current stream index. @@ -1506,8 +1522,16 @@ pub struct MultiInputsStream<'a> { } impl InputStream for MultiInputsStream<'_> { + /// Buffer a character. + fn unread(&mut self, ch: char) { + self.buf = Some(ch); + } /// Get the next character fn get_next(&mut self) -> Option { + if let Some(ch) = self.buf.take() { + return Some(ch); + } + loop { if self.index >= self.streams.len() { // No more streams @@ -1523,6 +1547,10 @@ impl InputStream for MultiInputsStream<'_> { } /// Peek the next character fn peek_next(&mut self) -> Option { + if let Some(ch) = self.buf { + return Some(ch); + } + loop { if self.index >= self.streams.len() { // No more streams @@ -1673,6 +1701,7 @@ pub fn lex<'a, 'e>( }, pos: Position::new(1, 0), stream: MultiInputsStream { + buf: None, streams: input.iter().map(|s| s.chars().peekable()).collect(), index: 0, }, diff --git a/tests/number_literals.rs b/tests/number_literals.rs index ce19b52d..2e7b6b01 100644 --- a/tests/number_literals.rs +++ b/tests/number_literals.rs @@ -4,7 +4,17 @@ use rhai::{Engine, EvalAltResult, INT}; fn test_number_literal() -> Result<(), Box> { let engine = Engine::new(); - assert_eq!(engine.eval::("65")?, 65); + assert_eq!(engine.eval::("42")?, 42); + + #[cfg(not(feature = "no_object"))] + assert_eq!( + engine.eval::("42.type_of()")?, + if cfg!(feature = "only_i32") { + "i32" + } else { + "i64" + } + ); Ok(()) } From 9fcbda1ba41f3f0eef749a1d299d736a66b76c3b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 24 Sep 2020 10:51:21 +0800 Subject: [PATCH 2/6] Add reverse function to arrays. --- doc/src/language/arrays.md | 1 + src/packages/array_basic.rs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/doc/src/language/arrays.md b/doc/src/language/arrays.md index 2e1b8cc9..1f60decb 100644 --- a/doc/src/language/arrays.md +++ b/doc/src/language/arrays.md @@ -41,6 +41,7 @@ The following methods (mostly defined in the [`BasicArrayPackage`][packages] but | `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, or returns [`()`] if the index is not valid | +| `reverse` | _none_ | reverses the array | | `len` method and property | _none_ | returns the number of elements | | `pad` | element to pad, target length | pads the array with an element to at least a specified length | | `clear` | _none_ | empties the array | diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 8b56be54..1647a156 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -133,6 +133,10 @@ mod array_functions { list.clear(); } } + #[inline(always)] + pub fn reverse(list: &mut Array) { + list.reverse(); + } } fn pad( From 12e9a8567d267831386d7bac3c4afeffd2239f95 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 24 Sep 2020 11:17:39 +0800 Subject: [PATCH 3/6] Make tables casing consistent. --- doc/src/appendix/keywords.md | 114 +++++++++++++++--------------- doc/src/appendix/operators.md | 49 +++++++------ doc/src/engine/options.md | 22 +++--- doc/src/language/arrays.md | 4 +- doc/src/language/fn-namespaces.md | 4 +- doc/src/language/keywords.md | 28 ++++---- doc/src/language/logic.md | 10 +-- doc/src/language/method.md | 2 +- doc/src/language/num-fn.md | 18 ++--- doc/src/language/num-op.md | 26 +++---- doc/src/plugins/function.md | 10 +-- doc/src/plugins/module.md | 38 +++++----- doc/src/rust/custom.md | 12 +++- doc/src/rust/getters-setters.md | 10 +-- doc/src/rust/indexers.md | 10 +-- doc/src/rust/packages/builtin.md | 26 +++---- doc/src/rust/print-custom.md | 16 ++--- doc/src/rust/register-raw.md | 12 ++-- doc/src/rust/traits.md | 8 +-- doc/src/safety/max-array-size.md | 2 +- doc/src/start/examples/rust.md | 28 ++++---- doc/src/start/examples/scripts.md | 38 +++++----- doc/src/start/features.md | 34 ++++----- 23 files changed, 264 insertions(+), 257 deletions(-) diff --git a/doc/src/appendix/keywords.md b/doc/src/appendix/keywords.md index d896739a..1b52d049 100644 --- a/doc/src/appendix/keywords.md +++ b/doc/src/appendix/keywords.md @@ -5,34 +5,34 @@ Keywords List | Keyword | Description | Inactive under | Overloadable | | :-------------------: | ---------------------------------------- | :-------------: | :----------: | -| `true` | Boolean true literal | | No | -| `false` | Boolean false literal | | No | -| `let` | Variable declaration | | No | -| `const` | Constant declaration | | No | -| `is_shared` | Is a value shared? | | No | -| `if` | If statement | | No | -| `else` | else block of if statement | | No | -| `while` | While loop | | No | -| `loop` | Infinite loop | | No | -| `for` | For loop | | No | -| `in` | Containment test, part of for loop | | No | -| `continue` | Continue a loop at the next iteration | | No | -| `break` | Loop breaking | | No | -| `return` | Return value | | No | -| `throw` | Throw exception | | No | -| `import` | Import module | [`no_module`] | No | -| `export` | Export variable | [`no_module`] | No | -| `as` | Alias for variable export | [`no_module`] | No | -| `private` | Mark function private | [`no_function`] | No | -| `fn` (lower-case `f`) | Function definition | [`no_function`] | No | -| `Fn` (capital `F`) | Function to create a [function pointer] | | Yes | -| `call` | Call a [function pointer] | | No | -| `curry` | Curry a [function pointer] | | No | -| `this` | Reference to base object for method call | [`no_function`] | No | -| `type_of` | Get type name of value | | Yes | -| `print` | Print value | | Yes | -| `debug` | Print value in debug format | | Yes | -| `eval` | Evaluate script | | Yes | +| `true` | boolean true literal | | no | +| `false` | boolean false literal | | no | +| `let` | variable declaration | | no | +| `const` | constant declaration | | no | +| `is_shared` | is a value shared? | | no | +| `if` | if statement | | no | +| `else` | else block of if statement | | no | +| `while` | while loop | | no | +| `loop` | infinite loop | | no | +| `for` | for loop | | no | +| `in` | containment test, part of for loop | | no | +| `continue` | continue a loop at the next iteration | | no | +| `break` | loop breaking | | no | +| `return` | return value | | no | +| `throw` | throw exception | | no | +| `import` | import module | [`no_module`] | no | +| `export` | export variable | [`no_module`] | no | +| `as` | alias for variable export | [`no_module`] | no | +| `private` | mark function private | [`no_function`] | no | +| `fn` (lower-case `f`) | function definition | [`no_function`] | no | +| `Fn` (capital `F`) | function to create a [function pointer] | | yes | +| `call` | call a [function pointer] | | no | +| `curry` | curry a [function pointer] | | no | +| `this` | reference to base object for method call | [`no_function`] | no | +| `type_of` | get type name of value | | yes | +| `print` | print value | | yes | +| `debug` | print value in debug format | | yes | +| `eval` | evaluate script | | yes | Reserved Keywords @@ -40,32 +40,32 @@ Reserved Keywords | Keyword | Potential usage | | --------- | --------------------- | -| `var` | Variable declaration | -| `static` | Variable declaration | -| `shared` | Share value | -| `do` | Looping | -| `each` | Looping | -| `then` | Control flow | -| `goto` | Control flow | -| `exit` | Control flow | -| `switch` | Matching | -| `match` | Matching | -| `case` | Matching | -| `public` | Function/field access | -| `new` | Constructor | -| `try` | Trap exception | -| `catch` | Catch exception | -| `use` | Import namespace | -| `with` | Scope | -| `module` | Module | -| `package` | Package | -| `spawn` | Threading | -| `go` | Threading | -| `await` | Async | -| `async` | Async | -| `sync` | Async | -| `yield` | Async | -| `default` | Special value | -| `void` | Special value | -| `null` | Special value | -| `nil` | Special value | +| `var` | variable declaration | +| `static` | variable declaration | +| `shared` | share value | +| `do` | looping | +| `each` | looping | +| `then` | control flow | +| `goto` | control flow | +| `exit` | control flow | +| `switch` | matching | +| `match` | matching | +| `case` | matching | +| `public` | function/field access | +| `new` | constructor | +| `try` | trap exception | +| `catch` | catch exception | +| `use` | import namespace | +| `with` | scope | +| `module` | module | +| `package` | package | +| `spawn` | threading | +| `go` | threading | +| `await` | async | +| `async` | async | +| `sync` | async | +| `yield` | async | +| `default` | special value | +| `void` | special value | +| `null` | special value | +| `nil` | special value | diff --git a/doc/src/appendix/operators.md b/doc/src/appendix/operators.md index b4303d79..812966a8 100644 --- a/doc/src/appendix/operators.md +++ b/doc/src/appendix/operators.md @@ -9,29 +9,28 @@ Operators | Operator | Description | Binary? | Binding direction | | :---------------: | ------------------------------ | :-----: | :---------------: | -| `+` | Add | Yes | Left | -| `-` | Subtract, Minus | Yes/No | Left | -| `*` | Multiply | Yes | Left | -| `/` | Divide | Yes | Left | -| `%` | Modulo | Yes | Left | -| `~` | Power | Yes | Left | -| `>>` | Right bit-shift | Yes | Left | -| `<<` | Left bit-shift | Yes | Left | -| `&` | Bit-wise _And_, Boolean _And_ | Yes | Left | -| \| | Bit-wise _Or_, Boolean _Or_ | Yes | Left | -| `^` | Bit-wise _Xor_, Boolean _Xor_ | Yes | Left | -| `==` | Equals to | Yes | Left | -| `~=` | Not equals to | Yes | Left | -| `>` | Greater than | Yes | Left | -| `>=` | Greater than or equals to | Yes | Left | -| `<` | Less than | Yes | Left | -| `<=` | Less than or equals to | Yes | Left | -| `>=` | Greater than or equals to | Yes | Left | -| `&&` | Boolean _And_ (short-circuits) | Yes | Left | -| \|\| | Boolean _Or_ (short-circuits) | Yes | Left | -| `!` | Boolean _Not_ | No | Left | -| `[` .. `]` | Indexing | Yes | Right | -| `.` | Property access, Method call | Yes | Right | +| `+` | add | yes | left | +| `-` | subtract, Minus | yes/no | left | +| `*` | multiply | yes | left | +| `/` | divide | yes | left | +| `%` | modulo | yes | left | +| `~` | power | yes | left | +| `>>` | right bit-shift | yes | left | +| `<<` | left bit-shift | yes | left | +| `&` | bit-wise _And_, boolean _And_ | yes | left | +| \| | bit-wise _Or_, boolean _Or_ | yes | left | +| `^` | bit-wise _Xor_, boolean _Xor_ | yes | left | +| `==` | equals to | yes | left | +| `~=` | not equals to | yes | left | +| `>` | greater than | yes | left | +| `>=` | greater than or equals to | yes | left | +| `<` | less than | yes | left | +| `<=` | less than or equals to | yes | left | +| `&&` | boolean _And_ (short-circuits) | yes | left | +| \|\| | boolean _Or_ (short-circuits) | yes | left | +| `!` | boolean _Not_ | no | left | +| `[` .. `]` | indexing | yes | right | +| `.` | property access, method call | yes | right | Symbols @@ -39,8 +38,8 @@ Symbols | Symbol | Description | | ------------ | ------------------------ | -| `:` | Property value separator | -| `::` | Module path separator | +| `:` | property value separator | +| `::` | module path separator | | `#` | _Reserved_ | | `=>` | _Reserved_ | | `->` | _Reserved_ | diff --git a/doc/src/engine/options.md b/doc/src/engine/options.md index a87e1d21..46f55236 100644 --- a/doc/src/engine/options.md +++ b/doc/src/engine/options.md @@ -5,14 +5,14 @@ Engine Configuration Options A number of other configuration options are available from the `Engine` to fine-tune behavior and safeguards. -| Method | Not available under | Description | -| ------------------------ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------ | -| `set_optimization_level` | [`no_optimize`] | Set the amount of script _optimizations_ performed. See [script optimization]. | -| `set_max_expr_depths` | [`unchecked`] | Set the maximum nesting levels of an expression/statement. See [maximum statement depth]. | -| `set_max_call_levels` | [`unchecked`] | Set the maximum number of function call levels (default 50) to avoid infinite recursion. See [maximum call stack depth]. | -| `set_max_operations` | [`unchecked`] | Set the maximum number of _operations_ that a script is allowed to consume. See [maximum number of operations]. | -| `set_max_modules` | [`unchecked`] | Set the maximum number of [modules] that a script is allowed to load. See [maximum number of modules]. | -| `set_max_string_size` | [`unchecked`] | Set the maximum length (in UTF-8 bytes) for [strings]. See [maximum length of strings]. | -| `set_max_array_size` | [`unchecked`], [`no_index`] | Set the maximum size for [arrays]. See [maximum size of arrays]. | -| `set_max_map_size` | [`unchecked`], [`no_object`] | Set the maximum number of properties for [object maps]. See [maximum size of object maps]. | -| `disable_symbol` | | Disable a certain keyword or operator. See [disable keywords and operators]. | +| Method | Not available under | Description | +| ------------------------ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| `set_optimization_level` | [`no_optimize`] | sets the amount of script _optimizations_ performed. See [script optimization]. | +| `set_max_expr_depths` | [`unchecked`] | sets the maximum nesting levels of an expression/statement. See [maximum statement depth]. | +| `set_max_call_levels` | [`unchecked`] | sets the maximum number of function call levels (default 50) to avoid infinite recursion. See [maximum call stack depth]. | +| `set_max_operations` | [`unchecked`] | sets the maximum number of _operations_ that a script is allowed to consume. See [maximum number of operations]. | +| `set_max_modules` | [`unchecked`] | sets the maximum number of [modules] that a script is allowed to load. See [maximum number of modules]. | +| `set_max_string_size` | [`unchecked`] | sets the maximum length (in UTF-8 bytes) for [strings]. See [maximum length of strings]. | +| `set_max_array_size` | [`unchecked`], [`no_index`] | sets the maximum size for [arrays]. See [maximum size of arrays]. | +| `set_max_map_size` | [`unchecked`], [`no_object`] | sets the maximum number of properties for [object maps]. See [maximum size of object maps]. | +| `disable_symbol` | | disables a certain keyword or operator. See [disable keywords and operators]. | diff --git a/doc/src/language/arrays.md b/doc/src/language/arrays.md index 1f60decb..a4a8e458 100644 --- a/doc/src/language/arrays.md +++ b/doc/src/language/arrays.md @@ -37,7 +37,7 @@ The following methods (mostly defined in the [`BasicArrayPackage`][packages] but | `append` | array to append | concatenates the second array to the end of the first | | `+=` operator | array, array to append | concatenates the second array to the end of the first | | `+` operator | first array, second array | concatenates the first array with the second | -| `insert` | element to insert, position
(beginning if <= 0, end if >= length) | insert an element at a certain index | +| `insert` | element to insert, 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, or returns [`()`] if the index is not valid | @@ -52,7 +52,7 @@ Use Custom Types With Arrays --------------------------- To use a [custom type] with arrays, a number of array functions need to be manually implemented, -in particular `push`, `pad` and the `+=` operator. In addition, the `==` operator must be +in particular `push`, `insert`, `pad` and the `+=` operator. In addition, the `==` operator must be implemented for the [custom type] in order to support the `in` operator which uses `==` to compare elements. diff --git a/doc/src/language/fn-namespaces.md b/doc/src/language/fn-namespaces.md index 9516f1e6..15d86ddb 100644 --- a/doc/src/language/fn-namespaces.md +++ b/doc/src/language/fn-namespaces.md @@ -17,8 +17,8 @@ In general, there are two types of _namespaces_ where functions are looked up: | Namespace | Source | Lookup method | How Many | | --------- | ---------------------------------------------------------------------- | --------------------------------- | :----------------------: | -| Global | `Engine::register_XXX` API, [`AST`] being evaluated, [packages] loaded | Simple function name | One | -| Module | [`Module`] | Namespace-qualified function name | As many as [`import`]-ed | +| Global | `Engine::register_XXX` API, [`AST`] being evaluated, [packages] loaded | simple function name | one | +| Module | [`Module`] | namespace-qualified function name | as many as [`import`]-ed | Global Namespace diff --git a/doc/src/language/keywords.md b/doc/src/language/keywords.md index 4ddaae8f..cb18852c 100644 --- a/doc/src/language/keywords.md +++ b/doc/src/language/keywords.md @@ -7,19 +7,19 @@ The following are reserved keywords in Rhai: | Active keywords | Reserved keywords | Usage | Inactive under feature | | ------------------------------------------------- | ------------------------------------------------ | --------------------- | :--------------------: | -| `true`, `false` | | Boolean constants | | -| `let`, `const` | `var`, `static` | Variable declarations | | -| `is_shared` | | Shared values | [`no_closure`] | -| `if`, `else` | `then`, `goto`, `exit` | Control flow | | -| | `switch`, `match`, `case` | Matching | | -| `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | Looping | | -| `fn`, `private` | `public`, `new` | Functions | [`no_function`] | -| `return` | | Return values | | -| `throw` | `try`, `catch` | Throw exceptions | | -| `import`, `export`, `as` | `use`, `with`, `module`, `package` | Modules/packages | [`no_module`] | -| `Fn`, `call`, `curry` | | Function pointers | | -| | `spawn`, `go`, `sync`, `async`, `await`, `yield` | Threading/async | | -| `type_of`, `print`, `debug`, `eval` | | Special functions | | -| | `default`, `void`, `null`, `nil` | Special values | | +| `true`, `false` | | boolean constants | | +| `let`, `const` | `var`, `static` | variable declarations | | +| `is_shared` | | shared values | [`no_closure`] | +| `if`, `else` | `then`, `goto`, `exit` | control flow | | +| | `switch`, `match`, `case` | matching | | +| `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | looping | | +| `fn`, `private` | `public`, `new` | functions | [`no_function`] | +| `return` | | return values | | +| `throw` | `try`, `catch` | throw exceptions | | +| `import`, `export`, `as` | `use`, `with`, `module`, `package` | modules/packages | [`no_module`] | +| `Fn`, `call`, `curry` | | function pointers | | +| | `spawn`, `go`, `sync`, `async`, `await`, `yield` | threading/async | | +| `type_of`, `print`, `debug`, `eval` | | special functions | | +| | `default`, `void`, `null`, `nil` | special values | | Keywords cannot become the name of a [function] or [variable], even when they are disabled. diff --git a/doc/src/language/logic.md b/doc/src/language/logic.md index 699b9827..498cab0d 100644 --- a/doc/src/language/logic.md +++ b/doc/src/language/logic.md @@ -45,11 +45,11 @@ Boolean operators | Operator | Description | | ----------------- | ------------------------------------- | -| `!` | Boolean _Not_ | -| `&&` | Boolean _And_ (short-circuits) | -| \|\| | Boolean _Or_ (short-circuits) | -| `&` | Boolean _And_ (doesn't short-circuit) | -| \| | Boolean _Or_ (doesn't short-circuit) | +| `!` | boolean _Not_ | +| `&&` | boolean _And_ (short-circuits) | +| \|\| | boolean _Or_ (short-circuits) | +| `&` | boolean _And_ (doesn't short-circuit) | +| \| | boolean _Or_ (doesn't short-circuit) | Double boolean operators `&&` and `||` _short-circuit_, meaning that the second operand will not be evaluated if the first one already proves the condition wrong. diff --git a/doc/src/language/method.md b/doc/src/language/method.md index d2b4281d..4b88fe68 100644 --- a/doc/src/language/method.md +++ b/doc/src/language/method.md @@ -49,7 +49,7 @@ The following table illustrates the differences: | Function type | Parameters | Object reference | Function signature | | :-----------: | :--------: | :--------------------: | :-----------------------------------------------------: | -| Native Rust | _n_ + 1 | First `&mut` parameter | `fn method`
`(obj: &mut T, x: U, y: V) {}` | +| Native Rust | _n_ + 1 | first `&mut` parameter | `fn method`
`(obj: &mut T, x: U, y: V) {}` | | Rhai script | _n_ | `this` | `fn method(x, y) {}` | diff --git a/doc/src/language/num-fn.md b/doc/src/language/num-fn.md index df656053..599b03c1 100644 --- a/doc/src/language/num-fn.md +++ b/doc/src/language/num-fn.md @@ -9,10 +9,10 @@ Integer Functions The following standard functions (defined in the [`BasicMathPackage`][packages] but excluded if using a [raw `Engine`]) operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only: -| Function | No available under | Description | -| -------- | :----------------: | ---------------------------------------------------------------------- | -| `abs` | | absolute value | -| `sign` | | return -1 (`INT`) if the number is negative, +1 if positive, 0 if zero | +| Function | No available under | Description | +| -------- | :----------------: | ----------------------------------------------------------------------- | +| `abs` | | absolute value | +| `sign` | | returns -1 (`INT`) if the number is negative, +1 if positive, 0 if zero | Floating-Point Functions @@ -39,8 +39,8 @@ Conversion Functions The following standard functions (defined in the [`BasicMathPackage`][packages] but excluded if using a [raw `Engine`]) parse numbers: -| Function | No available under | Description | -| --------------- | :----------------: | -------------------------------------------------- | -| [`to_float`] | [`no_float`] | convert an integer type to `FLOAT` | -| [`parse_int`] | | convert a [string] to `INT` with an optional radix | -| [`parse_float`] | [`no_float`] | convert a [string] to `FLOAT` | +| Function | No available under | Description | +| --------------- | :----------------: | --------------------------------------------------- | +| [`to_float`] | [`no_float`] | converts an integer type to `FLOAT` | +| [`parse_int`] | | converts a [string] to `INT` with an optional radix | +| [`parse_float`] | [`no_float`] | converts a [string] to `FLOAT` | diff --git a/doc/src/language/num-op.md b/doc/src/language/num-op.md index f1db4d46..a897c7ae 100644 --- a/doc/src/language/num-op.md +++ b/doc/src/language/num-op.md @@ -10,8 +10,8 @@ Unary Operators | Operator | Description | | -------- | ----------- | -| `+` | Positive | -| `-` | Negative | +| `+` | positive | +| `-` | negative | ```rust let number = -5; @@ -24,17 +24,17 @@ Binary Operators | Operator | Description | Integers only | | --------------- | ---------------------------------------------------- | :-----------: | -| `+` | Plus | | -| `-` | Minus | | -| `*` | Multiply | | -| `/` | Divide (integer division if acting on integer types) | | -| `%` | Modulo (remainder) | | -| `~` | Power | | -| `&` | Bit-wise _And_ | Yes | -| \| | Bit-wise _Or_ | Yes | -| `^` | Bit-wise _Xor_ | Yes | -| `<<` | Left bit-shift | Yes | -| `>>` | Right bit-shift | Yes | +| `+` | plus | | +| `-` | minus | | +| `*` | multiply | | +| `/` | divide (integer division if acting on integer types) | | +| `%` | modulo (remainder) | | +| `~` | power | | +| `&` | bit-wise _And_ | Yes | +| \| | bit-wise _Or_ | Yes | +| `^` | bit-wise _Xor_ | Yes | +| `<<` | left bit-shift | Yes | +| `>>` | right bit-shift | Yes | ```rust let x = (1 + 2) * (6 - 4) / 2; // arithmetic, with parentheses diff --git a/doc/src/plugins/function.md b/doc/src/plugins/function.md index 391d97fb..d0d9b5fd 100644 --- a/doc/src/plugins/function.md +++ b/doc/src/plugins/function.md @@ -11,11 +11,11 @@ individual functions instead of a full-blown [plugin module]. Macros ------ -| Macro | Apply to | Behavior | -| ----------------------- | --------------------------------------------------------------- | -------------------------------------------------------- | -| `#[export_fn]` | Rust function defined in a Rust module | Export the function | -| `register_exported_fn!` | [`Engine`] instance, register name string, use path to function | Register function into an [`Engine`] under specific name | -| `set_exported_fn!` | [`Module`] instance, register name string, use path to function | Register function into an [`Module`] under specific name | +| Macro | Apply to | Description | +| ----------------------- | --------------------------------------------------------------- | ------------------------------------------------------------- | +| `#[export_fn]` | rust function defined in a Rust module | exports the function | +| `register_exported_fn!` | [`Engine`] instance, register name string, use path to function | registers the function into an [`Engine`] under specific name | +| `set_exported_fn!` | [`Module`] instance, register name string, use path to function | registers the function into an [`Module`] under specific name | `#[export_fn]` and `register_exported_fn!` diff --git a/doc/src/plugins/module.md b/doc/src/plugins/module.md index 65bb0014..2d001f68 100644 --- a/doc/src/plugins/module.md +++ b/doc/src/plugins/module.md @@ -204,11 +204,11 @@ The above function can be called in five ways: | Parameter for `#[rhai_fn(...)]` | Type | Call style | | ------------------------------- | :-------------: | --------------------------------------------- | -| `name = "get_prop_value"` | Method function | `get_prop_value(x, 0)`, `x.get_prop_value(0)` | -| `name = "prop"` | Method function | `prop(x, 0)`, `x.prop(0)` | -| `name = "+"` | Operator | `x + 42` | -| `set = "prop"` | Setter | `x.prop = 42` | -| `index_get` | Index getter | `x[0]` | +| `name = "get_prop_value"` | method function | `get_prop_value(x, 0)`, `x.get_prop_value(0)` | +| `name = "prop"` | method function | `prop(x, 0)`, `x.prop(0)` | +| `name = "+"` | operator | `x + 42` | +| `set = "prop"` | setter | `x.prop = 42` | +| `index_get` | index getter | `x[0]` | Fallible Functions @@ -244,11 +244,11 @@ mod my_module { Parameters can be applied to the `#[export_module]` attribute to override its default behavior. -| Parameter | Behavior | -| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -| _None_ | Export only public (i.e. `pub`) functions | -| `export_all` | Export all functions (including private, non-`pub` functions); use `#[rhai_fn(skip)]` on individual functions to avoid export | -| `export_prefix = "..."` | Export functions (including private, non-`pub` functions) with names starting with a specific prefix | +| Parameter | Description | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------ | +| _none_ | exports only public (i.e. `pub`) functions | +| `export_all` | exports all functions (including private, non-`pub` functions); use `#[rhai_fn(skip)]` on individual functions to avoid export | +| `export_prefix = "..."` | exports functions (including private, non-`pub` functions) with names starting with a specific prefix | Inner Attributes @@ -260,12 +260,12 @@ Inner attributes can be applied to the inner items of a module to tweak the expo Parameters should be set on inner attributes to specify the desired behavior. -| Attribute Parameter | Use with | Apply to | Behavior | -| ------------------- | --------------------------- | -------------------------------------------------------- | ----------------------------------------------------- | -| `skip` | `#[rhai_fn]`, `#[rhai_mod]` | Function or sub-module | Do not export this function/sub-module | -| `name = "..."` | `#[rhai_fn]`, `#[rhai_mod]` | Function or sub-module | Register function/sub-module under the specified name | -| `get = "..."` | `#[rhai_fn]` | Function with `&mut` first parameter | Register a getter for the named property | -| `set = "..."` | `#[rhai_fn]` | Function with `&mut` first parameter | Register a setter for the named property | -| `index_get` | `#[rhai_fn]` | Function with `&mut` first parameter | Register an index getter | -| `index_set` | `#[rhai_fn]` | Function with `&mut` first parameter | Register an index setter | -| `return_raw` | `#[rhai_fn]` | Function returning `Result>` | Mark this as a [fallible function] | +| Attribute Parameter | Use with | Apply to | Description | +| ------------------- | --------------------------- | -------------------------------------------------------- | ------------------------------------------------------ | +| `skip` | `#[rhai_fn]`, `#[rhai_mod]` | function or sub-module | do not export this function/sub-module | +| `name = "..."` | `#[rhai_fn]`, `#[rhai_mod]` | function or sub-module | registers function/sub-module under the specified name | +| `get = "..."` | `#[rhai_fn]` | function with `&mut` first parameter | registers a getter for the named property | +| `set = "..."` | `#[rhai_fn]` | function with `&mut` first parameter | registers a setter for the named property | +| `index_get` | `#[rhai_fn]` | function with `&mut` first parameter | registers an index getter | +| `index_set` | `#[rhai_fn]` | function with `&mut` first parameter | registers an index setter | +| `return_raw` | `#[rhai_fn]` | function returning `Result>` | marks this as a [fallible function] | diff --git a/doc/src/rust/custom.md b/doc/src/rust/custom.md index 824588d5..eb548f69 100644 --- a/doc/src/rust/custom.md +++ b/doc/src/rust/custom.md @@ -161,8 +161,8 @@ x.type_of() == "Hello"; Use the Custom Type With Arrays ------------------------------ -The `push` and `pad` functions, as well as the `+=` operator, for [arrays] are only defined for -standard built-in types. For custom types, type-specific versions must be registered: +The `push`, `insert`, `pad` functions, as well as the `+=` operator, for [arrays] are only +defined for standard built-in types. For custom types, type-specific versions must be registered: ```rust engine @@ -170,6 +170,14 @@ engine list.push(Dynamic::from(item)); }).register_fn("+=", |list: &mut Array, item: TestStruct| { list.push(Dynamic::from(item)); + }).register_fn("insert", |list: &mut Array, position: i64, item: TestStruct| { + if position <= 0 { + list.insert(0, Dynamic::from(item)); + } else if (position as usize) >= list.len() - 1 { + list.push(item); + } else { + list.insert(position as usize, Dynamic::from(item)); + } }).register_fn("pad", |list: &mut Array, len: i64, item: TestStruct| { if len as usize > list.len() { list.resize(len as usize, item); diff --git a/doc/src/rust/getters-setters.md b/doc/src/rust/getters-setters.md index f1e95c92..efc19514 100644 --- a/doc/src/rust/getters-setters.md +++ b/doc/src/rust/getters-setters.md @@ -11,11 +11,11 @@ Getters and setters are disabled when the [`no_object`] feature is used. | `Engine` API | Description | Return Value of Function | | --------------------- | ------------------------------------------------- | :-----------------------------------: | -| `register_get` | Register a getter | _Any_ | -| `register_set` | Register a setter | _None_ | -| `register_get_set` | Short-hand to register both a getter and a setter | _None_ | -| `register_get_result` | Register a getter | `Result>` | -| `register_set_result` | Register a setter | `Result<(), Box>` | +| `register_get` | register a getter | _any_ | +| `register_set` | register a setter | _none_ | +| `register_get_set` | short-hand to register both a getter and a setter | _none_ | +| `register_get_result` | register a getter | `Result>` | +| `register_set_result` | register a setter | `Result<(), Box>` | Cannot Override Object Maps diff --git a/doc/src/rust/indexers.md b/doc/src/rust/indexers.md index 3906bf7a..ca880cff 100644 --- a/doc/src/rust/indexers.md +++ b/doc/src/rust/indexers.md @@ -15,11 +15,11 @@ Indexers are disabled when the [`no_index`] feature is used. | `Engine` API | Description | Return Value of Function | | ----------------------------- | -------------------------------------------------------- | :-----------------------------------: | -| `register_indexer_get` | Register an index getter | _Any_ | -| `register_indexer_set` | Register an index setter | _None_ | -| `register_indexer_get_set` | Short-hand to register both an index getter and a setter | _None_ | -| `register_indexer_get_result` | Register an index getter | `Result>` | -| `register_indexer_set_result` | Register an index setter | `Result<(), Box>` | +| `register_indexer_get` | register an index getter | _any_ | +| `register_indexer_set` | register an index setter | _none_ | +| `register_indexer_get_set` | short-hand to register both an index getter and a setter | _none_ | +| `register_indexer_get_result` | register an index getter | `Result>` | +| `register_indexer_set_result` | register an index setter | `Result<(), Box>` | Cannot Override Arrays, Object Maps and Strings diff --git a/doc/src/rust/packages/builtin.md b/doc/src/rust/packages/builtin.md index 4b4fba81..6f80fc1a 100644 --- a/doc/src/rust/packages/builtin.md +++ b/doc/src/rust/packages/builtin.md @@ -9,19 +9,19 @@ Built-In Packages | Package | Description | In `Core` | In `Standard` | | ---------------------- | ------------------------------------------------------------------------------------------------------ | :-------: | :-----------: | -| `ArithmeticPackage` | Arithmetic operators (e.g. `+`, `-`, `*`, `/`) for numeric types that are not built in (e.g. `u16`) | Yes | Yes | -| `BasicIteratorPackage` | Numeric ranges (e.g. `range(1, 10)`) | Yes | Yes | -| `LogicPackage` | Logical and comparison operators (e.g. `==`, `>`) for numeric types that are not built in (e.g. `u16`) | Yes | Yes | -| `BasicStringPackage` | Basic string functions (e.g. `print`, `debug`, `len`) that are not built in | Yes | Yes | -| `BasicTimePackage` | Basic time functions (e.g. [timestamps]) | Yes | Yes | -| `MoreStringPackage` | Additional string functions, including converting common types to string | No | Yes | -| `BasicMathPackage` | Basic math functions (e.g. `sin`, `sqrt`) | No | Yes | -| `BasicArrayPackage` | Basic [array] functions (not available under `no_index`) | No | Yes | -| `BasicMapPackage` | Basic [object map] functions (not available under `no_object`) | No | Yes | -| `BasicFnPackage` | Basic methods for [function pointers]. | Yes | Yes | -| `EvalPackage` | Disable [`eval`] | No | No | -| `CorePackage` | Basic essentials | Yes | Yes | -| `StandardPackage` | Standard library (default for `Engine::new`) | No | Yes | +| `ArithmeticPackage` | arithmetic operators (e.g. `+`, `-`, `*`, `/`) for numeric types that are not built in (e.g. `u16`) | yes | yes | +| `BasicIteratorPackage` | numeric ranges (e.g. `range(1, 10)`) | yes | yes | +| `LogicPackage` | logical and comparison operators (e.g. `==`, `>`) for numeric types that are not built in (e.g. `u16`) | yes | yes | +| `BasicStringPackage` | basic string functions (e.g. `print`, `debug`, `len`) that are not built in | yes | yes | +| `BasicTimePackage` | basic time functions (e.g. [timestamps]) | yes | yes | +| `MoreStringPackage` | additional string functions, including converting common types to string | no | yes | +| `BasicMathPackage` | basic math functions (e.g. `sin`, `sqrt`) | no | yes | +| `BasicArrayPackage` | basic [array] functions (not available under `no_index`) | no | yes | +| `BasicMapPackage` | basic [object map] functions (not available under `no_object`) | no | yes | +| `BasicFnPackage` | basic methods for [function pointers]. | yes | yes | +| `EvalPackage` | disable [`eval`] | no | no | +| `CorePackage` | basic essentials | yes | yes | +| `StandardPackage` | standard library (default for `Engine::new`) | no | yes | Load the `CorePackage` diff --git a/doc/src/rust/print-custom.md b/doc/src/rust/print-custom.md index c729c49d..2aab1b92 100644 --- a/doc/src/rust/print-custom.md +++ b/doc/src/rust/print-custom.md @@ -7,11 +7,11 @@ To use custom types for [`print`] and [`debug`], or convert its value into a [st it is necessary that the following functions be registered (assuming the custom type is `T : Display + Debug`): -| Function | Signature | Typical implementation | Usage | -| ----------- | ------------------------------------------------------------- | ------------------------------------- | --------------------------------------------------------------------------------------- | -| `to_string` | \|s: &mut T\| -> ImmutableString | `s.to_string().into()` | Converts the custom type into a [string] | -| `print` | \|s: &mut T\| -> ImmutableString | `s.to_string().into()` | Converts the custom type into a [string] for the [`print`] statement | -| `debug` | \|s: &mut T\| -> ImmutableString | `format!("{:?}", s).into()` | Converts the custom type into a [string] for the [`debug`] statement | -| `+` | \|s1: ImmutableString, s: T\| -> ImmutableString | `s1 + s` | Append the custom type to another [string], for `print("Answer: " + type);` usage | -| `+` | \|s: T, s2: ImmutableString\| -> ImmutableString | `s.to_string().push_str(&s2).into();` | Append another [string] to the custom type, for `print(type + " is the answer");` usage | -| `+=` | \|s1: &mut ImmutableString, s: T\| | `s1 += s.to_string()` | Append the custom type to an existing [string], for `s += type;` usage | +| Function | Signature | Typical implementation | Usage | +| ----------- | ------------------------------------------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------- | +| `to_string` | \|s: &mut T\| -> ImmutableString | `s.to_string().into()` | converts the custom type into a [string] | +| `print` | \|s: &mut T\| -> ImmutableString | `s.to_string().into()` | converts the custom type into a [string] for the [`print`] statement | +| `debug` | \|s: &mut T\| -> ImmutableString | `format!("{:?}", s).into()` | converts the custom type into a [string] for the [`debug`] statement | +| `+` | \|s1: ImmutableString, s: T\| -> ImmutableString | `s1 + s` | appends the custom type to another [string], for `print("Answer: " + type);` usage | +| `+` | \|s: T, s2: ImmutableString\| -> ImmutableString | `s.to_string().push_str(&s2).into();` | appends another [string] to the custom type, for `print(type + " is the answer");` usage | +| `+=` | \|s1: &mut ImmutableString, s: T\| | `s1 += s.to_string()` | appends the custom type to an existing [string], for `s += type;` usage | diff --git a/doc/src/rust/register-raw.md b/doc/src/rust/register-raw.md index 210f419f..965a103b 100644 --- a/doc/src/rust/register-raw.md +++ b/doc/src/rust/register-raw.md @@ -84,12 +84,12 @@ Extract Arguments To extract an argument from the `args` parameter (`&mut [&mut Dynamic]`), use the following: -| Argument type | Access (`n` = argument position) | Result | -| ------------------------------ | ------------------------------------- | ---------------------------------------------------------- | -| [Primary type][standard types] | `args[n].clone().cast::()` | Copy of value. | -| Custom type | `args[n].read_lock::().unwrap()` | Immutable reference to value. | -| Custom type (consumed) | `std::mem::take(args[n]).cast::()` | The _consumed_ value.
The original value becomes `()`. | -| `this` object | `args[0].write_lock::().unwrap()` | Mutable reference to value. | +| Argument type | Access (`n` = argument position) | Result | +| ------------------------------ | ------------------------------------- | --------------------------------------------------------- | +| [Primary type][standard types] | `args[n].clone().cast::()` | copy of value | +| Custom type | `args[n].read_lock::().unwrap()` | immutable reference to value | +| Custom type (consumed) | `std::mem::take(args[n]).cast::()` | the _consumed_ value.
The original value becomes `()` | +| `this` object | `args[0].write_lock::().unwrap()` | mutable reference to value | When there is a mutable reference to the `this` object (i.e. the first argument), there can be no other immutable references to `args`, otherwise the Rust borrow checker will complain. diff --git a/doc/src/rust/traits.md b/doc/src/rust/traits.md index 88231146..4d0d8391 100644 --- a/doc/src/rust/traits.md +++ b/doc/src/rust/traits.md @@ -7,7 +7,7 @@ A number of traits, under the `rhai::` module namespace, provide additional func | Trait | Description | Methods | | ------------------ | ---------------------------------------------------------------------------------------- | --------------------------------------- | -| `RegisterFn` | Trait for registering functions | `register_fn` | -| `RegisterResultFn` | Trait for registering fallible functions returning `Result>` | `register_result_fn` | -| `Func` | Trait for creating Rust closures from script | `create_from_ast`, `create_from_script` | -| `ModuleResolver` | Trait implemented by module resolution services | `resolve` | +| `RegisterFn` | trait for registering functions | `register_fn` | +| `RegisterResultFn` | trait for registering fallible functions returning `Result>` | `register_result_fn` | +| `Func` | trait for creating Rust closures from script | `create_from_ast`, `create_from_script` | +| `ModuleResolver` | trait implemented by module resolution services | `resolve` | diff --git a/doc/src/safety/max-array-size.md b/doc/src/safety/max-array-size.md index d2724e0f..a50a3745 100644 --- a/doc/src/safety/max-array-size.md +++ b/doc/src/safety/max-array-size.md @@ -19,7 +19,7 @@ This check can be disabled via the [`unchecked`] feature for higher performance ```rust let mut engine = Engine::new(); -engine.set_max_array_size(500); // allow arrays only up to 500 items +engine.set_max_array_size(500); // allow arrays only up to 500 items engine.set_max_array_size(0); // allow unlimited arrays ``` diff --git a/doc/src/start/examples/rust.md b/doc/src/start/examples/rust.md index 157cffed..bd3064c8 100644 --- a/doc/src/start/examples/rust.md +++ b/doc/src/start/examples/rust.md @@ -5,17 +5,17 @@ Rust Examples A number of examples can be found in the `examples` directory: -| Example | Description | -| ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | -| [`arrays_and_structs`]({{repoTree}}/examples/arrays_and_structs.rs) | Shows how to register a custom Rust type and using [arrays] on it. | -| [`custom_types_and_methods`]({{repoTree}}/examples/custom_types_and_methods.rs) | Shows how to register a custom Rust type and methods for it. | -| [`hello`]({{repoTree}}/examples/hello.rs) | Simple example that evaluates an expression and prints the result. | -| [`reuse_scope`]({{repoTree}}/examples/reuse_scope.rs) | Evaluates two pieces of code in separate runs, but using a common [`Scope`]. | -| [`rhai_runner`]({{repoTree}}/examples/rhai_runner.rs) | Runs each filename passed to it as a Rhai script. | -| [`serde`]({{repoTree}}/examples/serde.rs) | Example to serialize and deserialize Rust types with [`serde`](https://crates.io/crates/serde).
The [`serde`] feature is required to run. | -| [`simple_fn`]({{repoTree}}/examples/simple_fn.rs) | Shows how to register a simple function. | -| [`strings`]({{repoTree}}/examples/strings.rs) | Shows different ways to register functions taking string arguments. | -| [`repl`]({{repoTree}}/examples/repl.rs) | A simple REPL, interactively evaluate statements from stdin. | +| Example | Description | +| ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| [`arrays_and_structs`]({{repoTree}}/examples/arrays_and_structs.rs) | shows how to register a custom Rust type and using [arrays] on it | +| [`custom_types_and_methods`]({{repoTree}}/examples/custom_types_and_methods.rs) | shows how to register a custom Rust type and methods for it | +| [`hello`]({{repoTree}}/examples/hello.rs) | simple example that evaluates an expression and prints the result | +| [`reuse_scope`]({{repoTree}}/examples/reuse_scope.rs) | evaluates two pieces of code in separate runs, but using a common [`Scope`] | +| [`rhai_runner`]({{repoTree}}/examples/rhai_runner.rs) | runs each filename passed to it as a Rhai script | +| [`serde`]({{repoTree}}/examples/serde.rs) | example to serialize and deserialize Rust types with [`serde`](https://crates.io/crates/serde).
The [`serde`] feature is required to run | +| [`simple_fn`]({{repoTree}}/examples/simple_fn.rs) | shows how to register a simple function | +| [`strings`]({{repoTree}}/examples/strings.rs) | shows different ways to register functions taking string arguments | +| [`repl`]({{repoTree}}/examples/repl.rs) | a simple REPL, interactively evaluate statements from stdin | The `repl` example is a particularly good one as it allows one to interactively try out Rhai's language features in a standard REPL (**R**ead-**E**val-**P**rint **L**oop). @@ -35,9 +35,9 @@ cargo run --example {example_name} To illustrate `no-std` builds, a number of sample applications are available under the `no_std` directory: -| Sample | Description | Optimization | Allocator | Panics | -| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------- | :----------: | :-----------------------------------------------: | :----: | -| [`no_std_test`]({{repoTree}}/no_std/no_std_test) | Bare-bones test application that evaluates a Rhai expression and sets the result as the return value. | Size | [`wee_alloc`](https://crates.io/crates/wee_alloc) | Abort | +| Sample | Description | Optimization | Allocator | Panics | +| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------- | :----------: | :-----------------------------------------------: | :----: | +| [`no_std_test`]({{repoTree}}/no_std/no_std_test) | bare-bones test application that evaluates a Rhai expression and sets the result as the return value | size | [`wee_alloc`](https://crates.io/crates/wee_alloc) | abort | `cargo run` cannot be used to run a `no-std` sample. It must first be built: diff --git a/doc/src/start/examples/scripts.md b/doc/src/start/examples/scripts.md index fff32275..d748d8a0 100644 --- a/doc/src/start/examples/scripts.md +++ b/doc/src/start/examples/scripts.md @@ -10,22 +10,22 @@ There are also a number of examples scripts that showcase Rhai's features, all i | Script | Description | | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | -| [`array.rhai`]({{repoTree}}/scripts/array.rhai) | [Arrays] | -| [`assignment.rhai`]({{repoTree}}/scripts/assignment.rhai) | Variable declarations | -| [`comments.rhai`]({{repoTree}}/scripts/comments.rhai) | Just comments | +| [`array.rhai`]({{repoTree}}/scripts/array.rhai) | [arrays] | +| [`assignment.rhai`]({{repoTree}}/scripts/assignment.rhai) | variable declarations | +| [`comments.rhai`]({{repoTree}}/scripts/comments.rhai) | just comments | | [`for1.rhai`]({{repoTree}}/scripts/for1.rhai) | [`for`]({{rootUrl}}/language/for.md) loops | | [`for2.rhai`]({{repoTree}}/scripts/for2.rhai) | [`for`]({{rootUrl}}/language/for.md) loops on [arrays] | -| [`function_decl1.rhai`]({{repoTree}}/scripts/function_decl1.rhai) | A [function] without parameters | -| [`function_decl2.rhai`]({{repoTree}}/scripts/function_decl2.rhai) | A [function] with two parameters | -| [`function_decl3.rhai`]({{repoTree}}/scripts/function_decl3.rhai) | A [function] with many parameters | +| [`function_decl1.rhai`]({{repoTree}}/scripts/function_decl1.rhai) | a [function] without parameters | +| [`function_decl2.rhai`]({{repoTree}}/scripts/function_decl2.rhai) | a [function] with two parameters | +| [`function_decl3.rhai`]({{repoTree}}/scripts/function_decl3.rhai) | a [function] with many parameters | | [`if1.rhai`]({{repoTree}}/scripts/if1.rhai) | [`if`]({{rootUrl}}/language/if.md) example | -| [`loop.rhai`]({{repoTree}}/scripts/loop.rhai) | Count-down [`loop`]({{rootUrl}}/language/loop.md) in Rhai, emulating a `do` .. `while` loop | -| [`oop.rhai`]({{repoTree}}/scripts/oop.rhai) | Simulate [object-oriented programming (OOP)][OOP] with [closures] | -| [`op1.rhai`]({{repoTree}}/scripts/op1.rhai) | Just simple addition | -| [`op2.rhai`]({{repoTree}}/scripts/op2.rhai) | Simple addition and multiplication | -| [`op3.rhai`]({{repoTree}}/scripts/op3.rhai) | Change evaluation order with parenthesis | -| [`string.rhai`]({{repoTree}}/scripts/string.rhai) | [String] operations | -| [`strings_map.rhai`]({{repoTree}}/scripts/strings_map.rhai) | [String] and [object map] operations | +| [`loop.rhai`]({{repoTree}}/scripts/loop.rhai) | count-down [`loop`]({{rootUrl}}/language/loop.md) in Rhai, emulating a `do` .. `while` loop | +| [`oop.rhai`]({{repoTree}}/scripts/oop.rhai) | simulate [object-oriented programming (OOP)][OOP] with [closures] | +| [`op1.rhai`]({{repoTree}}/scripts/op1.rhai) | just simple addition | +| [`op2.rhai`]({{repoTree}}/scripts/op2.rhai) | simple addition and multiplication | +| [`op3.rhai`]({{repoTree}}/scripts/op3.rhai) | change evaluation order with parenthesis | +| [`string.rhai`]({{repoTree}}/scripts/string.rhai) | [string] operations | +| [`strings_map.rhai`]({{repoTree}}/scripts/strings_map.rhai) | [string] and [object map] operations | | [`while.rhai`]({{repoTree}}/scripts/while.rhai) | [`while`]({{rootUrl}}/language/while.md) loop | @@ -34,12 +34,12 @@ Benchmark Scripts The following scripts are for benchmarking the speed of Rhai: -| Scripts | Description | -| --------------------------------------------------------- | --------------------------------------------------------------------------------------- | -| [`speed_test.rhai`]({{repoTree}}/scripts/speed_test.rhai) | A simple application to measure the speed of Rhai's interpreter (1 million iterations). | -| [`primes.rhai`]({{repoTree}}/scripts/primes.rhai) | Use Sieve of Eratosthenes to find all primes smaller than a limit. | -| [`fibonacci.rhai`]({{repoTree}}/scripts/fibonacci.rhai) | Calculate the n-th Fibonacci number using a really dumb algorithm. | -| [`mat_mul.rhai`]({{repoTree}}/scripts/mat_mul.rhai) | Matrix multiplication test to measure the speed of multi-dimensional array access. | +| Scripts | Description | +| --------------------------------------------------------- | -------------------------------------------------------------------------------------- | +| [`speed_test.rhai`]({{repoTree}}/scripts/speed_test.rhai) | a simple application to measure the speed of Rhai's interpreter (1 million iterations) | +| [`primes.rhai`]({{repoTree}}/scripts/primes.rhai) | use Sieve of Eratosthenes to find all primes smaller than a limit | +| [`fibonacci.rhai`]({{repoTree}}/scripts/fibonacci.rhai) | calculate the n-th Fibonacci number using a really dumb algorithm | +| [`mat_mul.rhai`]({{repoTree}}/scripts/mat_mul.rhai) | matrix multiplication test to measure the speed of multi-dimensional array access | Running Example Scripts diff --git a/doc/src/start/features.md b/doc/src/start/features.md index 9cd86f52..baf1d176 100644 --- a/doc/src/start/features.md +++ b/doc/src/start/features.md @@ -11,23 +11,23 @@ Notice that this deviates from Rust norm where features are _additive_. Excluding unneeded functionalities can result in smaller, faster builds as well as more control over what a script can (or cannot) do. -| Feature | Additive? | Description | -| ------------------- | :-------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `unchecked` | No | Disable arithmetic checking (such as over-flows and division by zero), call stack depth limit, operations count limit and modules loading limit.
Beware that a bad script may panic the entire system! | -| `sync` | No | Restrict all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and [`AST`], are all `Send + Sync`. | -| `no_optimize` | No | Disable [script optimization]. | -| `no_float` | No | Disable floating-point numbers and math. | -| `only_i32` | No | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | -| `only_i64` | No | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | -| `no_index` | No | Disable [arrays] and indexing features. | -| `no_object` | No | Disable support for [custom types] and [object maps]. | -| `no_function` | No | Disable script-defined [functions]. | -| `no_module` | No | Disable loading external [modules]. | -| `no_closure` | No | Disable [capturing][automatic currying] external variables in [anonymous functions] to simulate _closures_, or [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls. | -| `no_std` | No | Build for `no-std` (implies `no_closure`). Notice that additional dependencies will be pulled in to replace `std` features. | -| `serde` | Yes | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. | -| `internals` | Yes | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. | -| `unicode-xid-ident` | No | Allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) as identifiers. | +| Feature | Additive? | Description | +| ------------------- | :-------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `unchecked` | no | disables arithmetic checking (such as over-flows and division by zero), call stack depth limit, operations count limit and modules loading limit.
Beware that a bad script may panic the entire system! | +| `sync` | no | restricts all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and [`AST`], are all `Send + Sync` | +| `no_optimize` | no | disables [script optimization] | +| `no_float` | no | disables floating-point numbers and math | +| `only_i32` | no | sets the system integer type to `i32` and disable all other integer types. `INT` is set to `i32` | +| `only_i64` | no | sets the system integer type to `i64` and disable all other integer types. `INT` is set to `i64` | +| `no_index` | no | disables [arrays] and indexing features | +| `no_object` | no | disables support for [custom types] and [object maps] | +| `no_function` | no | disables script-defined [functions] | +| `no_module` | no | disables loading external [modules] | +| `no_closure` | no | disables [capturing][automatic currying] external variables in [anonymous functions] to simulate _closures_, or [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls | +| `no_std` | no | builds for `no-std` (implies `no_closure`). Notice that additional dependencies will be pulled in to replace `std` features | +| `serde` | yes | enables serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies | +| `internals` | yes | exposes internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version | +| `unicode-xid-ident` | no | allows [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) as identifiers | Example From b8aeaa84de89fa558acfb4473064cd81ce590aad Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 24 Sep 2020 16:10:25 +0800 Subject: [PATCH 4/6] Add functions to iterate script function definitions. --- RELEASES.md | 1 + src/module.rs | 35 +++++++++++++++++++++-------------- src/parser.rs | 6 ++++++ 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 3ecbd4ab..56326471 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -25,6 +25,7 @@ New features * Plugins support via procedural macros. * Scripted functions are allowed in packages. * `parse_int` and `parse_float` functions. +* `AST::iter_functions` and `Module::iter_script_fn_info` to iterate functions. Version 0.18.3 diff --git a/src/module.rs b/src/module.rs index e03c11fa..dd089c91 100644 --- a/src/module.rs +++ b/src/module.rs @@ -1124,6 +1124,14 @@ impl Module { .map(|f| f.get_shared_fn_def()) } + #[cfg(not(feature = "no_function"))] + pub fn iter_script_fn_info(&self, action: impl Fn(FnAccess, &str, usize)) { + self.functions.iter().for_each(|(_, (_, _, _, v))| match v { + Func::Script(ref f) => action(f.access, f.name.as_str(), f.params.len()), + _ => (), + }); + } + /// Create a new `Module` by evaluating an `AST`. /// /// # Examples @@ -1493,24 +1501,23 @@ mod file { #[cfg(feature = "sync")] let c = self.cache.read().unwrap(); - match c.get(&file_path) { - Some(ast) => ( + if let Some(ast) = c.get(&file_path) { + ( Module::eval_ast_as_new(scope, ast, engine) .map_err(|err| err.new_position(pos))?, None, - ), - None => { - // Load the file and compile it if not found - let ast = engine - .compile_file(file_path.clone()) - .map_err(|err| err.new_position(pos))?; + ) + } else { + // Load the file and compile it if not found + let ast = engine + .compile_file(file_path.clone()) + .map_err(|err| err.new_position(pos))?; - ( - Module::eval_ast_as_new(scope, &ast, engine) - .map_err(|err| err.new_position(pos))?, - Some(ast), - ) - } + ( + Module::eval_ast_as_new(scope, &ast, engine) + .map_err(|err| err.new_position(pos))?, + Some(ast), + ) } }; diff --git a/src/parser.rs b/src/parser.rs index 2c0076df..d06daf1b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -299,6 +299,12 @@ impl AST { self.1.retain_functions(filter); } + /// Iterate through all functions + #[cfg(not(feature = "no_function"))] + pub fn iter_functions(&self, action: impl Fn(FnAccess, &str, usize)) { + self.1.iter_script_fn_info(action); + } + /// Clear all function definitions in the `AST`. #[cfg(not(feature = "no_function"))] pub fn clear_functions(&mut self) { From c4ec93080e58eb1f48ca4042e35f018ef209e1d3 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 24 Sep 2020 22:50:28 +0800 Subject: [PATCH 5/6] New FileModuleResolver. --- RELEASES.md | 3 +- doc/src/language/fn-namespaces.md | 57 +++-- doc/src/rust/modules/resolvers.md | 11 +- src/api.rs | 16 +- src/module.rs | 361 ++++++++++++++++++++++++------ src/optimize.rs | 2 +- src/parser.rs | 12 +- 7 files changed, 352 insertions(+), 110 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 56326471..78716df6 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -26,7 +26,8 @@ New features * Scripted functions are allowed in packages. * `parse_int` and `parse_float` functions. * `AST::iter_functions` and `Module::iter_script_fn_info` to iterate functions. - +* Functions iteration functions now take `FnMut` instead of `Fn`. +* New `FileModuleResolver` that encapsulates the entire `AST` of the module script, allowing function cross-calling. The old version is renamed `MergingFileModuleResolver`. Version 0.18.3 ============== diff --git a/doc/src/language/fn-namespaces.md b/doc/src/language/fn-namespaces.md index 15d86ddb..27f005c3 100644 --- a/doc/src/language/fn-namespaces.md +++ b/doc/src/language/fn-namespaces.md @@ -10,7 +10,7 @@ Each Function is a Separate Compilation Unit This means that individual functions can be separated, exported, re-grouped, imported, and generally mix-'n-match-ed with other completely unrelated scripts. -For example, the `AST::merge` method allows merging all functions in one [`AST`] into another, +For example, the `AST::merge` method allows Global all functions in one [`AST`] into another, forming a new, combined, group of functions. In general, there are two types of _namespaces_ where functions are looked up: @@ -43,10 +43,10 @@ This aspect is very similar to JavaScript before ES6 modules. // Compile a script into AST let ast1 = engine.compile( r#" - fn message() { "Hello!" } // greeting message + fn get_message() { "Hello!" } // greeting message fn say_hello() { - print(message()); // prints message + print(get_message()); // prints message } say_hello(); @@ -54,7 +54,7 @@ let ast1 = engine.compile( )?; // Compile another script with an overriding function -let ast2 = engine.compile(r#"fn message() { "Boo!" }"#)?; +let ast2 = engine.compile(r#"fn get_message() { "Boo!" }"#)?; // Merge the two AST's let ast = ast1.merge(ast2); // 'message' will be overwritten @@ -73,7 +73,7 @@ i.e. define the function in a separate module and then [`import`] it: | message.rhai | ---------------- -fn message() { "Hello!" } +fn get_message() { "Hello!" } --------------- @@ -82,40 +82,52 @@ fn message() { "Hello!" } fn say_hello() { import "message" as msg; - print(msg::message()); + print(msg::get_message()); } say_hello(); ``` -Module Namespaces ------------------ +Namespace Consideration When Not Using `FileModuleResolver` +--------------------------------------------------------- [Modules] can be dynamically loaded into a Rhai script using the [`import`] keyword. When that happens, functions defined within the [module] can be called with a _qualified_ name. -There is a catch, though, if functions in a module script refer to global functions -defined _within the script_. When called later, those functions will be searched in the -current global namespace and may not be found. +The [`FileModuleResolver`][module resolver] encapsulates the namespace inside the module itself, +so everything works as expected. A function defined in the module script cannot access functions +defined in the calling script, but it can freely call functions defined within the same module script. + +There is a catch, though. When using anything other than the [`FileModuleResolver`][module resolver], +functions in a module script refer to functions defined in the _global namespace_. +When called later, those functions may not be found. + +When using the [`GlobalFileModuleResolver`][module resolver], for example: ```rust +Using GlobalFileModuleResolver +============================== + ----------------- | greeting.rhai | ----------------- -fn message() { "Hello!" }; +fn get_message() { "Hello!" }; -fn say_hello() { print(message()); } - -say_hello(); // 'message' is looked up in the global namespace +fn say_hello() { + print(get_message()); // 'get_message' is looked up in the global namespace + // when exported +} +say_hello(); // Here, 'get_message' is found in the module namespace --------------- | script.rhai | --------------- import "greeting" as g; -g::say_hello(); // <- error: function not found - 'message' +g::say_hello(); // <- error: function not found - 'get_message' + // because it does not exist in the global namespace ``` In the example above, although the module `greeting.rhai` loads fine (`"Hello!"` is printed), @@ -123,22 +135,23 @@ the subsequent call using the _namespace-qualified_ function name fails to find '`message`' which now essentially becomes `g::message`. The call fails as there is no more function named '`message`' in the global namespace. -Therefore, when writing functions for a [module], make sure that those functions are as _pure_ -as possible and avoid cross-calling them from each other. A [function pointer] is a valid technique -to call another function within a module-defined function: +Therefore, when writing functions for a [module] intended for the [`GlobalFileModuleResolver`][module resolver], +make sure that those functions are as _pure_ as possible and avoid cross-calling them from each other. + +A [function pointer] is a valid technique to call another function in an environment-independent manner: ```rust ----------------- | greeting.rhai | ----------------- -fn message() { "Hello!" }; +fn get_message() { "Hello!" }; fn say_hello(msg_func) { // 'msg_func' is a function pointer print(msg_func.call()); // call via the function pointer } -say_hello(); // 'message' is looked up in the global namespace +say_hello(Fn("get_message")); --------------- @@ -149,7 +162,7 @@ import "greeting" as g; fn my_msg() { import "greeting" as g; // <- must import again here... - g::message() // <- ... otherwise will not find module 'g' + g::get_message() // <- ... otherwise will not find module 'g' } g::say_hello(Fn("my_msg")); // prints 'Hello!' diff --git a/doc/src/rust/modules/resolvers.md b/doc/src/rust/modules/resolvers.md index 4a3b97e7..92fa6074 100644 --- a/doc/src/rust/modules/resolvers.md +++ b/doc/src/rust/modules/resolvers.md @@ -16,11 +16,12 @@ which simply loads a script file based on the path (with `.rhai` extension attac Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace. -| Module Resolver | Description | -| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `FileModuleResolver` | The default module resolution service, not available under [`no_std`] or [WASM] builds. Loads a script file (based off the current directory) with `.rhai` extension.
The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.
`FileModuleResolver::create_module()` loads a script file and returns a module. | -| `StaticModuleResolver` | Loads modules that are statically added. This can be used under [`no_std`]. | -| `ModuleResolversCollection` | A collection of module resolvers. Modules will be resolved from each resolver in sequential order.
This is useful when multiple types of modules are needed simultaneously. | +| Module Resolver | Description | Namespace | +| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------: | +| `FileModuleResolver` | The default module resolution service, not available under [`no_std`] or [WASM] builds. Loads a script file (based off the current directory) with `.rhai` extension.
The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.
`FileModuleResolver::create_module()` loads a script file and returns a module. | Module (cannot access global namespace) | +| `GlobalFileModuleResolver` | A simpler but more efficient version of `FileModuleResolver`, intended for short utility modules. Not available under [`no_std`] or [WASM] builds. Loads a script file (based off the current directory) with `.rhai` extension.
**Note:** All functions are assumed absolutely _pure_ and cannot cross-call each other.
The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.
`FileModuleResolver::create_module()` loads a script file and returns a module. | Global | +| `StaticModuleResolver` | Loads modules that are statically added. This can be used under [`no_std`]. | Global | +| `ModuleResolversCollection` | A collection of module resolvers. Modules will be resolved from each resolver in sequential order.
This is useful when multiple types of modules are needed simultaneously. | Global | Set into `Engine` diff --git a/src/api.rs b/src/api.rs index 944b0be5..15998614 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1502,7 +1502,9 @@ impl Engine { args: A, ) -> Result> { let mut arg_values = args.into_vec(); - let result = self.call_fn_dynamic_raw(scope, ast, name, &mut None, arg_values.as_mut())?; + let mut args: StaticVec<_> = arg_values.as_mut().iter_mut().collect(); + + let result = self.call_fn_dynamic_raw(scope, ast, name, &mut None, args.as_mut())?; let typ = self.map_type_name(result.type_name()); @@ -1574,7 +1576,9 @@ impl Engine { mut this_ptr: Option<&mut Dynamic>, mut arg_values: impl AsMut<[Dynamic]>, ) -> FuncReturn { - self.call_fn_dynamic_raw(scope, lib, name, &mut this_ptr, arg_values.as_mut()) + let mut args: StaticVec<_> = arg_values.as_mut().iter_mut().collect(); + + self.call_fn_dynamic_raw(scope, lib, name, &mut this_ptr, args.as_mut()) } /// Call a script function defined in an `AST` with multiple `Dynamic` arguments. @@ -1592,16 +1596,14 @@ impl Engine { lib: impl AsRef, name: &str, this_ptr: &mut Option<&mut Dynamic>, - arg_values: &mut [Dynamic], + args: &mut [&mut Dynamic], ) -> FuncReturn { let lib = lib.as_ref(); - let mut args: StaticVec<_> = arg_values.iter_mut().collect(); let fn_def = get_script_function_by_signature(lib, name, args.len(), true) .ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::none()))?; let mut state = State::new(); let mut mods = Imports::new(); - let args = args.as_mut(); // Check for data race. if cfg!(not(feature = "no_closure")) { @@ -1634,8 +1636,8 @@ impl Engine { let lib = if cfg!(not(feature = "no_function")) { ast.lib() .iter_fn() - .filter(|(_, _, _, f)| f.is_script()) - .map(|(_, _, _, f)| f.get_fn_def().clone()) + .filter(|(_, _, _, _, f)| f.is_script()) + .map(|(_, _, _, _, f)| f.get_fn_def().clone()) .collect() } else { Default::default() diff --git a/src/module.rs b/src/module.rs index dd089c91..4d6cfa79 100644 --- a/src/module.rs +++ b/src/module.rs @@ -67,7 +67,11 @@ pub struct Module { all_variables: HashMap, /// External Rust functions. - functions: HashMap, Func), StraightHasherBuilder>, + functions: HashMap< + u64, + (String, FnAccess, usize, Option>, Func), + StraightHasherBuilder, + >, /// Iterator functions, keyed by the type producing the iterator. type_iterators: HashMap, @@ -97,7 +101,7 @@ impl fmt::Debug for Module { .join(", "), self.functions .values() - .map(|(_, _, _, f)| f.to_string()) + .map(|(_, _, _, _, f)| f.to_string()) .collect::>() .join(", "), ) @@ -258,20 +262,22 @@ impl Module { /// Set a script-defined function into the module. /// /// If there is an existing function of the same name and number of arguments, it is replaced. - pub(crate) fn set_script_fn(&mut self, fn_def: ScriptFnDef) -> &mut Self { + pub(crate) fn set_script_fn(&mut self, fn_def: ScriptFnDef) -> u64 { // None + function name + number of arguments. - let hash_script = calc_fn_hash(empty(), &fn_def.name, fn_def.params.len(), empty()); + let num_params = fn_def.params.len(); + let hash_script = calc_fn_hash(empty(), &fn_def.name, num_params, empty()); self.functions.insert( hash_script, ( fn_def.name.to_string(), fn_def.access, - Default::default(), + num_params, + None, fn_def.into(), ), ); self.indexed = false; - self + hash_script } /// Does a sub-module exist in the module? @@ -362,7 +368,7 @@ impl Module { } else if public_only { self.functions .get(&hash_fn) - .map(|(_, access, _, _)| match access { + .map(|(_, access, _, _, _)| match access { FnAccess::Public => true, FnAccess::Private => false, }) @@ -412,7 +418,7 @@ impl Module { let hash_fn = calc_fn_hash(empty(), &name, args_len, arg_types.iter().cloned()); self.functions - .insert(hash_fn, (name, access, params, func.into())); + .insert(hash_fn, (name, access, args_len, Some(params), func.into())); self.indexed = false; @@ -487,6 +493,31 @@ impl Module { self.set_fn(name, Public, arg_types, Func::from_method(Box::new(f))) } + /// Set a raw function but with a signature that is a scripted function, but the implementation is in Rust. + pub(crate) fn set_raw_fn_as_scripted( + &mut self, + name: impl Into, + num_args: 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_args, empty()); + let f = move |engine: &Engine, lib: &Module, args: &mut FnCallArgs| func(engine, lib, args); + self.functions.insert( + hash_script, + ( + name, + FnAccess::Public, + num_args, + None, + Func::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. @@ -979,7 +1010,7 @@ impl Module { } else { self.functions .get(&hash_fn) - .and_then(|(_, access, _, f)| match access { + .and_then(|(_, access, _, _, f)| match access { _ if !public_only => Some(f), FnAccess::Public => Some(f), FnAccess::Private => None, @@ -1028,14 +1059,14 @@ impl Module { /// Merge another module into this module. pub fn merge(&mut self, other: &Self) -> &mut Self { - self.merge_filtered(other, &|_, _, _| true) + self.merge_filtered(other, &mut |_, _, _| true) } /// Merge another module into this module, with only selected script-defined functions based on a filter predicate. pub(crate) fn merge_filtered( &mut self, other: &Self, - _filter: &impl Fn(FnAccess, &str, usize) -> bool, + mut _filter: &mut impl FnMut(FnAccess, &str, usize) -> bool, ) -> &mut Self { #[cfg(not(feature = "no_function"))] for (k, v) in &other.modules { @@ -1055,7 +1086,7 @@ impl Module { other .functions .iter() - .filter(|(_, (_, _, _, v))| match v { + .filter(|(_, (_, _, _, _, v))| match v { #[cfg(not(feature = "no_function"))] Func::Script(ref f) => _filter(f.access, f.name.as_str(), f.params.len()), _ => true, @@ -1076,9 +1107,9 @@ impl Module { #[cfg(not(feature = "no_function"))] pub(crate) fn retain_functions( &mut self, - filter: impl Fn(FnAccess, &str, usize) -> bool, + mut filter: impl FnMut(FnAccess, &str, usize) -> bool, ) -> &mut Self { - self.functions.retain(|_, (_, _, _, v)| match v { + self.functions.retain(|_, (_, _, _, _, v)| match v { Func::Script(ref f) => filter(f.access, f.name.as_str(), f.params.len()), _ => true, }); @@ -1110,7 +1141,7 @@ impl Module { /// Get an iterator to the functions in the module. pub(crate) fn iter_fn( &self, - ) -> impl Iterator, Func)> { + ) -> impl Iterator>, Func)> { self.functions.values() } @@ -1119,17 +1150,19 @@ impl Module { pub fn iter_script_fn<'a>(&'a self) -> impl Iterator> + 'a { self.functions .values() - .map(|(_, _, _, f)| f) + .map(|(_, _, _, _, f)| f) .filter(|f| f.is_script()) .map(|f| f.get_shared_fn_def()) } #[cfg(not(feature = "no_function"))] - pub fn iter_script_fn_info(&self, action: impl Fn(FnAccess, &str, usize)) { - self.functions.iter().for_each(|(_, (_, _, _, v))| match v { - Func::Script(ref f) => action(f.access, f.name.as_str(), f.params.len()), - _ => (), - }); + pub fn iter_script_fn_info(&self, mut action: impl FnMut(FnAccess, &str, usize)) { + self.functions + .iter() + .for_each(|(_, (_, _, _, _, v))| match v { + Func::Script(ref f) => action(f.access, f.name.as_str(), f.params.len()), + _ => (), + }); } /// Create a new `Module` by evaluating an `AST`. @@ -1202,7 +1235,7 @@ impl Module { variables.push((hash_var, value.clone())); } // Index all Rust functions - for (name, access, params, func) in module.functions.values() { + for (&hash, (name, access, num_args, params, func)) in module.functions.iter() { match access { // Private functions are not exported FnAccess::Private => continue, @@ -1210,31 +1243,31 @@ impl Module { } #[cfg(not(feature = "no_function"))] - if func.is_script() { - let fn_def = func.get_shared_fn_def(); - // Qualifiers + function name + number of arguments. - let hash_qualified_script = calc_fn_hash( - qualifiers.iter().map(|&v| v), - &fn_def.name, - fn_def.params.len(), - empty(), - ); - functions.push((hash_qualified_script, fn_def.into())); + if params.is_none() { + let hash_qualified_script = if qualifiers.is_empty() { + hash + } else { + // Qualifiers + function name + number of arguments. + calc_fn_hash(qualifiers.iter().map(|&v| v), &name, *num_args, empty()) + }; + functions.push((hash_qualified_script, func.clone())); continue; } - // Qualified Rust functions are indexed in two steps: - // 1) Calculate a hash in a similar manner to script-defined functions, - // i.e. qualifiers + function name + number of arguments. - let hash_qualified_script = - calc_fn_hash(qualifiers.iter().map(|&v| v), name, params.len(), empty()); - // 2) Calculate a second hash with no qualifiers, empty function name, - // zero number of arguments, and the actual list of argument `TypeId`'.s - let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned()); - // 3) The final hash is the XOR of the two hashes. - let hash_qualified_fn = hash_qualified_script ^ hash_fn_args; + if let Some(params) = params { + // Qualified Rust functions are indexed in two steps: + // 1) Calculate a hash in a similar manner to script-defined functions, + // i.e. qualifiers + function name + number of arguments. + let hash_qualified_script = + calc_fn_hash(qualifiers.iter().map(|&v| v), name, params.len(), empty()); + // 2) Calculate a second hash with no qualifiers, empty function name, + // zero number of arguments, and the actual list of argument `TypeId`'.s + let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned()); + // 3) The final hash is the XOR of the two hashes. + let hash_qualified_fn = hash_qualified_script ^ hash_fn_args; - functions.push((hash_qualified_fn, func.clone())); + functions.push((hash_qualified_fn, func.clone())); + } } } @@ -1349,7 +1382,7 @@ pub mod resolvers { pub use super::collection::ModuleResolversCollection; #[cfg(not(feature = "no_std"))] #[cfg(not(target_arch = "wasm32"))] - pub use super::file::FileModuleResolver; + pub use super::file::{FileModuleResolver, GlobalFileModuleResolver}; pub use super::stat::StaticModuleResolver; } #[cfg(feature = "no_module")] @@ -1363,6 +1396,181 @@ mod file { use super::*; use crate::stdlib::path::PathBuf; + /// Module resolution service that loads module script files from the file system. + /// + /// All functions in each module are treated as strictly _pure_ and cannot refer to + /// other functions within the same module. Functions are searched in the _global_ namespace. + /// + /// For simple utility libraries, this usually performs better than the full `FileModuleResolver`. + /// + /// Script files are cached so they are are not reloaded and recompiled in subsequent requests. + /// + /// The `new_with_path` and `new_with_path_and_extension` constructor functions + /// allow specification of a base directory with module path used as a relative path offset + /// to the base directory. The script file is then forced to be in a specified extension + /// (default `.rhai`). + /// + /// # Examples + /// + /// ``` + /// use rhai::Engine; + /// use rhai::module_resolvers::GlobalFileModuleResolver; + /// + /// // Create a new 'GlobalFileModuleResolver' loading scripts from the 'scripts' subdirectory + /// // with file extension '.x'. + /// let resolver = GlobalFileModuleResolver::new_with_path_and_extension("./scripts", "x"); + /// + /// let mut engine = Engine::new(); + /// engine.set_module_resolver(Some(resolver)); + /// ``` + #[derive(Debug)] + pub struct GlobalFileModuleResolver { + path: PathBuf, + extension: String, + + #[cfg(not(feature = "sync"))] + cache: RefCell>, + + #[cfg(feature = "sync")] + cache: RwLock>, + } + + impl Default for GlobalFileModuleResolver { + fn default() -> Self { + Self::new_with_path(PathBuf::default()) + } + } + + impl GlobalFileModuleResolver { + /// Create a new `GlobalFileModuleResolver` with a specific base path. + /// + /// # Examples + /// + /// ``` + /// use rhai::Engine; + /// use rhai::module_resolvers::GlobalFileModuleResolver; + /// + /// // Create a new 'GlobalFileModuleResolver' loading scripts from the 'scripts' subdirectory + /// // with file extension '.rhai' (the default). + /// let resolver = GlobalFileModuleResolver::new_with_path("./scripts"); + /// + /// let mut engine = Engine::new(); + /// engine.set_module_resolver(Some(resolver)); + /// ``` + pub fn new_with_path>(path: P) -> Self { + Self::new_with_path_and_extension(path, "rhai") + } + + /// Create a new `GlobalFileModuleResolver` with a specific base path and file extension. + /// + /// The default extension is `.rhai`. + /// + /// # Examples + /// + /// ``` + /// use rhai::Engine; + /// use rhai::module_resolvers::GlobalFileModuleResolver; + /// + /// // Create a new 'GlobalFileModuleResolver' loading scripts from the 'scripts' subdirectory + /// // with file extension '.x'. + /// let resolver = GlobalFileModuleResolver::new_with_path_and_extension("./scripts", "x"); + /// + /// let mut engine = Engine::new(); + /// engine.set_module_resolver(Some(resolver)); + /// ``` + pub fn new_with_path_and_extension, E: Into>( + path: P, + extension: E, + ) -> Self { + Self { + path: path.into(), + extension: extension.into(), + cache: Default::default(), + } + } + + /// Create a new `GlobalFileModuleResolver` with the current directory as base path. + /// + /// # Examples + /// + /// ``` + /// use rhai::Engine; + /// use rhai::module_resolvers::GlobalFileModuleResolver; + /// + /// // Create a new 'GlobalFileModuleResolver' loading scripts from the current directory + /// // with file extension '.rhai' (the default). + /// let resolver = GlobalFileModuleResolver::new(); + /// + /// let mut engine = Engine::new(); + /// engine.set_module_resolver(Some(resolver)); + /// ``` + pub fn new() -> Self { + Default::default() + } + + /// Create a `Module` from a file path. + pub fn create_module>( + &self, + engine: &Engine, + path: &str, + ) -> Result> { + self.resolve(engine, path, Default::default()) + } + } + + impl ModuleResolver for GlobalFileModuleResolver { + fn resolve( + &self, + engine: &Engine, + path: &str, + pos: Position, + ) -> Result> { + // Construct the script file path + let mut file_path = self.path.clone(); + file_path.push(path); + file_path.set_extension(&self.extension); // Force extension + + let scope = Default::default(); + + // See if it is cached + let (module, ast) = { + #[cfg(not(feature = "sync"))] + let c = self.cache.borrow(); + #[cfg(feature = "sync")] + let c = self.cache.read().unwrap(); + + if let Some(ast) = c.get(&file_path) { + ( + Module::eval_ast_as_new(scope, ast, engine) + .map_err(|err| err.new_position(pos))?, + None, + ) + } else { + // Load the file and compile it if not found + let ast = engine + .compile_file(file_path.clone()) + .map_err(|err| err.new_position(pos))?; + + ( + Module::eval_ast_as_new(scope, &ast, engine) + .map_err(|err| err.new_position(pos))?, + Some(ast), + ) + } + }; + + if let Some(ast) = ast { + // Put it into the cache + #[cfg(not(feature = "sync"))] + self.cache.borrow_mut().insert(file_path, ast); + #[cfg(feature = "sync")] + self.cache.write().unwrap().insert(file_path, ast); + } + + Ok(module) + } + } + /// Module resolution service that loads module script files from the file system. /// /// Script files are cached so they are are not reloaded and recompiled in subsequent requests. @@ -1492,43 +1700,60 @@ mod file { file_path.push(path); file_path.set_extension(&self.extension); // Force extension - let scope = Default::default(); - // See if it is cached - let (module, ast) = { + let exists = { #[cfg(not(feature = "sync"))] let c = self.cache.borrow(); #[cfg(feature = "sync")] let c = self.cache.read().unwrap(); - if let Some(ast) = c.get(&file_path) { - ( - Module::eval_ast_as_new(scope, ast, engine) - .map_err(|err| err.new_position(pos))?, - None, - ) - } else { - // Load the file and compile it if not found - let ast = engine - .compile_file(file_path.clone()) - .map_err(|err| err.new_position(pos))?; - - ( - Module::eval_ast_as_new(scope, &ast, engine) - .map_err(|err| err.new_position(pos))?, - Some(ast), - ) - } + c.contains_key(&file_path) }; - if let Some(ast) = ast { + if !exists { + // Load the file and compile it if not found + let ast = engine + .compile_file(file_path.clone()) + .map_err(|err| err.new_position(pos))?; + // Put it into the cache #[cfg(not(feature = "sync"))] - self.cache.borrow_mut().insert(file_path, ast); + self.cache.borrow_mut().insert(file_path.clone(), ast); #[cfg(feature = "sync")] - self.cache.write().unwrap().insert(file_path, ast); + self.cache.write().unwrap().insert(file_path.clone(), ast); } + #[cfg(not(feature = "sync"))] + let c = self.cache.borrow(); + #[cfg(feature = "sync")] + let c = self.cache.read().unwrap(); + + let ast = c.get(&file_path).unwrap(); + + let mut module = Module::eval_ast_as_new(Scope::new(), ast, engine)?; + + ast.iter_functions(|access, name, num_args| match access { + FnAccess::Private => (), + FnAccess::Public => { + let fn_name = name.to_string(); + let ast_lib = ast.lib().clone(); + + module.set_raw_fn_as_scripted( + name, + num_args, + move |engine: &Engine, _, args: &mut [&mut Dynamic]| { + engine.call_fn_dynamic_raw( + &mut Scope::new(), + &ast_lib, + &fn_name, + &mut None, + args, + ) + }, + ); + } + }); + Ok(module) } } diff --git a/src/optimize.rs b/src/optimize.rs index 83f828f4..b45332f7 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -575,7 +575,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // First search in functions lib (can override built-in) // Cater for both normal function call style and method call style (one additional arguments) - let has_script_fn = cfg!(not(feature = "no_function")) && state.lib.iter_fn().find(|(_, _, _, f)| { + let has_script_fn = cfg!(not(feature = "no_function")) && state.lib.iter_fn().find(|(_, _, _, _,f)| { if !f.is_script() { return false; } let fn_def = f.get_fn_def(); fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len()) diff --git a/src/parser.rs b/src/parser.rs index d06daf1b..9366a3d6 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -130,10 +130,10 @@ impl AST { /// This operation is cheap because functions are shared. pub fn clone_functions_only_filtered( &self, - filter: impl Fn(FnAccess, &str, usize) -> bool, + mut filter: impl FnMut(FnAccess, &str, usize) -> bool, ) -> Self { let mut functions: Module = Default::default(); - functions.merge_filtered(&self.1, &filter); + functions.merge_filtered(&self.1, &mut filter); Self(Default::default(), functions) } @@ -250,7 +250,7 @@ impl AST { pub fn merge_filtered( &self, other: &Self, - filter: impl Fn(FnAccess, &str, usize) -> bool, + mut filter: impl FnMut(FnAccess, &str, usize) -> bool, ) -> Self { let Self(statements, functions) = self; @@ -266,7 +266,7 @@ impl AST { }; let mut functions = functions.clone(); - functions.merge_filtered(&other.1, &filter); + functions.merge_filtered(&other.1, &mut filter); Self::new(ast, functions) } @@ -295,13 +295,13 @@ impl AST { /// # } /// ``` #[cfg(not(feature = "no_function"))] - pub fn retain_functions(&mut self, filter: impl Fn(FnAccess, &str, usize) -> bool) { + pub fn retain_functions(&mut self, filter: impl FnMut(FnAccess, &str, usize) -> bool) { self.1.retain_functions(filter); } /// Iterate through all functions #[cfg(not(feature = "no_function"))] - pub fn iter_functions(&self, action: impl Fn(FnAccess, &str, usize)) { + pub fn iter_functions(&self, action: impl FnMut(FnAccess, &str, usize)) { self.1.iter_script_fn_info(action); } From 6a53c446d3c46dd254bbe920c65c9a026ae676f4 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 24 Sep 2020 23:32:54 +0800 Subject: [PATCH 6/6] Fix no_function build. --- src/module.rs | 19 +++++++++++-------- src/optimize.rs | 13 +++++++++---- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/module.rs b/src/module.rs index 4d6cfa79..74c2b8da 100644 --- a/src/module.rs +++ b/src/module.rs @@ -5,13 +5,13 @@ use crate::calc_fn_hash; use crate::engine::Engine; use crate::fn_native::{CallableFunction as Func, FnCallArgs, IteratorFn, SendSync}; use crate::fn_register::by_value as cast_arg; -use crate::parser::{FnAccess, FnAccess::Public, ScriptFnDef}; +use crate::parser::{FnAccess, FnAccess::Public}; use crate::result::EvalAltResult; use crate::token::{Position, Token}; use crate::utils::{ImmutableString, StaticVec, StraightHasherBuilder}; #[cfg(not(feature = "no_function"))] -use crate::fn_native::Shared; +use crate::{fn_native::Shared, parser::ScriptFnDef}; #[cfg(not(feature = "no_module"))] use crate::{ @@ -262,6 +262,7 @@ impl Module { /// Set a script-defined function into the module. /// /// If there is an existing function of the same name and number of arguments, it is replaced. + #[cfg(not(feature = "no_function"))] pub(crate) fn set_script_fn(&mut self, fn_def: ScriptFnDef) -> u64 { // None + function name + number of arguments. let num_params = fn_def.params.len(); @@ -494,6 +495,7 @@ impl Module { } /// Set a raw function but with a signature that is a scripted function, but the implementation is in Rust. + #[cfg(not(feature = "no_function"))] pub(crate) fn set_raw_fn_as_scripted( &mut self, name: impl Into, @@ -1235,7 +1237,7 @@ impl Module { variables.push((hash_var, value.clone())); } // Index all Rust functions - for (&hash, (name, access, num_args, params, func)) in module.functions.iter() { + for (&_hash, (name, access, _num_args, params, func)) in module.functions.iter() { match access { // Private functions are not exported FnAccess::Private => continue, @@ -1245,10 +1247,10 @@ impl Module { #[cfg(not(feature = "no_function"))] if params.is_none() { let hash_qualified_script = if qualifiers.is_empty() { - hash + _hash } else { // Qualifiers + function name + number of arguments. - calc_fn_hash(qualifiers.iter().map(|&v| v), &name, *num_args, empty()) + calc_fn_hash(qualifiers.iter().map(|&v| v), &name, *_num_args, empty()) }; functions.push((hash_qualified_script, func.clone())); continue; @@ -1730,15 +1732,16 @@ mod file { let ast = c.get(&file_path).unwrap(); - let mut module = Module::eval_ast_as_new(Scope::new(), ast, engine)?; + let mut _module = Module::eval_ast_as_new(Scope::new(), ast, engine)?; + #[cfg(not(feature = "no_function"))] ast.iter_functions(|access, name, num_args| match access { FnAccess::Private => (), FnAccess::Public => { let fn_name = name.to_string(); let ast_lib = ast.lib().clone(); - module.set_raw_fn_as_scripted( + _module.set_raw_fn_as_scripted( name, num_args, move |engine: &Engine, _, args: &mut [&mut Dynamic]| { @@ -1754,7 +1757,7 @@ mod file { } }); - Ok(module) + Ok(_module) } } } diff --git a/src/optimize.rs b/src/optimize.rs index b45332f7..60bfb2cc 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -7,10 +7,13 @@ use crate::engine::{ }; use crate::fn_native::FnPtr; use crate::module::Module; -use crate::parser::{map_dynamic_to_expr, Expr, ReturnType, ScriptFnDef, Stmt, AST}; +use crate::parser::{map_dynamic_to_expr, Expr, ScriptFnDef, Stmt, AST}; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; use crate::utils::StaticVec; +#[cfg(not(feature = "no_function"))] +use crate::parser::ReturnType; + #[cfg(feature = "internals")] use crate::parser::CustomExpr; @@ -749,7 +752,8 @@ pub fn optimize_into_ast( level }; - let lib = if cfg!(not(feature = "no_function")) { + #[cfg(not(feature = "no_function"))] + let lib = { let mut module = Module::new(); if !level.is_none() { @@ -811,10 +815,11 @@ pub fn optimize_into_ast( } module - } else { - Default::default() }; + #[cfg(feature = "no_function")] + let lib = Default::default(); + AST::new( match level { OptimizationLevel::None => statements,