Merge branch 'master' into plugins
This commit is contained in:
commit
6924b62939
197
README.md
197
README.md
@ -47,7 +47,7 @@ It doesn't attempt to be a new language. For example:
|
|||||||
|
|
||||||
* No classes. Well, Rust doesn't either. On the other hand...
|
* No classes. Well, Rust doesn't either. On the other hand...
|
||||||
* No traits... so it is also not Rust. Do your Rusty stuff in Rust.
|
* No traits... so it is also not Rust. Do your Rusty stuff in Rust.
|
||||||
* No structures - definte your types in Rust instead; Rhai can seamless work with _any Rust type_.
|
* No structures - define your types in Rust instead; Rhai can seamlessly work with _any Rust type_.
|
||||||
* No first-class functions - Code your functions in Rust instead, and register them with Rhai.
|
* No first-class functions - Code your functions in Rust instead, and register them with Rhai.
|
||||||
* No closures - do your closure magic in Rust instead; [turn a Rhai scripted function into a Rust closure](#calling-rhai-functions-from-rust).
|
* No closures - do your closure magic in Rust instead; [turn a Rhai scripted function into a Rust closure](#calling-rhai-functions-from-rust).
|
||||||
* It is best to expose an API in Rhai for scripts to call. All your core functionalities should be in Rust.
|
* It is best to expose an API in Rhai for scripts to call. All your core functionalities should be in Rust.
|
||||||
@ -300,7 +300,7 @@ let ast = engine.compile(true,
|
|||||||
r"
|
r"
|
||||||
// a function with two parameters: String and i64
|
// a function with two parameters: String and i64
|
||||||
fn hello(x, y) {
|
fn hello(x, y) {
|
||||||
x.len() + y
|
x.len + y
|
||||||
}
|
}
|
||||||
|
|
||||||
// functions can be overloaded: this one takes only one parameter
|
// functions can be overloaded: this one takes only one parameter
|
||||||
@ -355,7 +355,7 @@ use rhai::{Engine, Func}; // use 'Func' for 'create_from_s
|
|||||||
|
|
||||||
let engine = Engine::new(); // create a new 'Engine' just for this
|
let engine = Engine::new(); // create a new 'Engine' just for this
|
||||||
|
|
||||||
let script = "fn calc(x, y) { x + y.len() < 42 }";
|
let script = "fn calc(x, y) { x + y.len < 42 }";
|
||||||
|
|
||||||
// Func takes two type parameters:
|
// Func takes two type parameters:
|
||||||
// 1) a tuple made up of the types of the script function's parameters
|
// 1) a tuple made up of the types of the script function's parameters
|
||||||
@ -945,8 +945,8 @@ If the [`no_object`] feature is turned on, however, the _method_ style of functi
|
|||||||
(i.e. calling a function as an object-method) is no longer supported.
|
(i.e. calling a function as an object-method) is no longer supported.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Below is a syntax error under 'no_object' because 'len' cannot be called in method style.
|
// Below is a syntax error under 'no_object' because 'clear' cannot be called in method style.
|
||||||
let result = engine.eval::<i64>("let x = [1, 2, 3]; x.len()")?;
|
let result = engine.eval::<()>("let x = [1, 2, 3]; x.clear()")?;
|
||||||
```
|
```
|
||||||
|
|
||||||
[`type_of()`] works fine with custom types and returns the name of the type.
|
[`type_of()`] works fine with custom types and returns the name of the type.
|
||||||
@ -1082,7 +1082,7 @@ fn main() -> Result<(), Box<EvalAltResult>>
|
|||||||
|
|
||||||
// First invocation
|
// First invocation
|
||||||
engine.eval_with_scope::<()>(&mut scope, r"
|
engine.eval_with_scope::<()>(&mut scope, r"
|
||||||
let x = 4 + 5 - y + z + s.len();
|
let x = 4 + 5 - y + z + s.len;
|
||||||
y = 1;
|
y = 1;
|
||||||
")?;
|
")?;
|
||||||
|
|
||||||
@ -1122,6 +1122,7 @@ Comments
|
|||||||
--------
|
--------
|
||||||
|
|
||||||
Comments are C-style, including '`/*` ... `*/`' pairs and '`//`' for comments to the end of the line.
|
Comments are C-style, including '`/*` ... `*/`' pairs and '`//`' for comments to the end of the line.
|
||||||
|
Comments can be nested.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let /* intruder comment */ name = "Bob";
|
let /* intruder comment */ name = "Bob";
|
||||||
@ -1138,14 +1139,35 @@ let /* intruder comment */ name = "Bob";
|
|||||||
*/
|
*/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Keywords
|
||||||
|
--------
|
||||||
|
|
||||||
|
The following are reserved keywords in Rhai:
|
||||||
|
|
||||||
|
| Keywords | Usage | Not available under feature |
|
||||||
|
| ------------------------------------------------- | --------------------- | :-------------------------: |
|
||||||
|
| `true`, `false` | Boolean constants | |
|
||||||
|
| `let`, `const` | Variable declarations | |
|
||||||
|
| `if`, `else` | Control flow | |
|
||||||
|
| `while`, `loop`, `for`, `in`, `continue`, `break` | Looping | |
|
||||||
|
| `fn`, `private` | Functions | [`no_function`] |
|
||||||
|
| `return` | Return values | |
|
||||||
|
| `throw` | Return errors | |
|
||||||
|
| `import`, `export`, `as` | Modules | [`no_module`] |
|
||||||
|
|
||||||
|
Keywords cannot be the name of a [function] or [variable], unless the relevant exclusive feature is enabled.
|
||||||
|
For example, `fn` is a valid variable name if the [`no_function`] feature is used.
|
||||||
|
|
||||||
Statements
|
Statements
|
||||||
----------
|
----------
|
||||||
|
|
||||||
Statements are terminated by semicolons '`;`' - they are mandatory, except for the _last_ statement where it can be omitted.
|
Statements are terminated by semicolons '`;`' and they are mandatory,
|
||||||
|
except for the _last_ statement in a _block_ (enclosed by '`{`' .. '`}`' pairs) where it can be omitted.
|
||||||
|
|
||||||
A statement can be used anywhere where an expression is expected. The _last_ statement of a statement block
|
A statement can be used anywhere where an expression is expected. These are called, for lack of a more
|
||||||
(enclosed by '`{`' .. '`}`' pairs) is always the return value of the statement. If a statement has no return value
|
creative name, "statement expressions." The _last_ statement of a statement block is _always_ the block's
|
||||||
(e.g. variable definitions, assignments) then the value will be [`()`].
|
return value when used as a statement.
|
||||||
|
If the last statement has no return value (e.g. variable definitions, assignments) then it is assumed to be [`()`].
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let a = 42; // normal assignment statement
|
let a = 42; // normal assignment statement
|
||||||
@ -1153,16 +1175,17 @@ let a = foo(42); // normal function call statement
|
|||||||
foo < 42; // normal expression as statement
|
foo < 42; // normal expression as statement
|
||||||
|
|
||||||
let a = { 40 + 2 }; // 'a' is set to the value of the statement block, which is the value of the last statement
|
let a = { 40 + 2 }; // 'a' is set to the value of the statement block, which is the value of the last statement
|
||||||
// ^ notice that the last statement does not require a terminating semicolon (although it also works with it)
|
// ^ the last statement does not require a terminating semicolon (although it also works with it)
|
||||||
// ^ notice that a semicolon is required here to terminate the assignment statement; it is syntax error without it
|
// ^ semicolon required here to terminate the assignment statement; it is a syntax error without it
|
||||||
|
|
||||||
4 * 10 + 2 // this is also a statement, which is an expression, with no ending semicolon because
|
4 * 10 + 2 // a statement which is just one expression; no ending semicolon is OK
|
||||||
// it is the last statement of the whole block
|
// because it is the last statement of the whole block
|
||||||
```
|
```
|
||||||
|
|
||||||
Variables
|
Variables
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
[variable]: #variables
|
||||||
[variables]: #variables
|
[variables]: #variables
|
||||||
|
|
||||||
Variables in Rhai follow normal C naming rules (i.e. must contain only ASCII letters, digits and underscores '`_`').
|
Variables in Rhai follow normal C naming rules (i.e. must contain only ASCII letters, digits and underscores '`_`').
|
||||||
@ -1289,16 +1312,16 @@ Floating-point functions
|
|||||||
|
|
||||||
The following standard functions (defined in the [`BasicMathPackage`](#packages) but excluded if using a [raw `Engine`]) operate on `f64` only:
|
The following standard functions (defined in the [`BasicMathPackage`](#packages) but excluded if using a [raw `Engine`]) operate on `f64` only:
|
||||||
|
|
||||||
| Category | Functions |
|
| Category | Functions |
|
||||||
| ---------------- | ------------------------------------------------------------ |
|
| ---------------- | --------------------------------------------------------------------- |
|
||||||
| Trigonometry | `sin`, `cos`, `tan`, `sinh`, `cosh`, `tanh` in degrees |
|
| Trigonometry | `sin`, `cos`, `tan`, `sinh`, `cosh`, `tanh` in degrees |
|
||||||
| Arc-trigonometry | `asin`, `acos`, `atan`, `asinh`, `acosh`, `atanh` in degrees |
|
| Arc-trigonometry | `asin`, `acos`, `atan`, `asinh`, `acosh`, `atanh` in degrees |
|
||||||
| Square root | `sqrt` |
|
| Square root | `sqrt` |
|
||||||
| Exponential | `exp` (base _e_) |
|
| Exponential | `exp` (base _e_) |
|
||||||
| Logarithmic | `ln` (base _e_), `log10` (base 10), `log` (any base) |
|
| Logarithmic | `ln` (base _e_), `log10` (base 10), `log` (any base) |
|
||||||
| Rounding | `floor`, `ceiling`, `round`, `int`, `fraction` |
|
| Rounding | `floor`, `ceiling`, `round`, `int`, `fraction` methods and properties |
|
||||||
| Conversion | [`to_int`] |
|
| Conversion | [`to_int`] |
|
||||||
| Testing | `is_nan`, `is_finite`, `is_infinite` |
|
| Testing | `is_nan`, `is_finite`, `is_infinite` methods and properties |
|
||||||
|
|
||||||
Strings and Chars
|
Strings and Chars
|
||||||
-----------------
|
-----------------
|
||||||
@ -1307,8 +1330,8 @@ Strings and Chars
|
|||||||
[strings]: #strings-and-chars
|
[strings]: #strings-and-chars
|
||||||
[char]: #strings-and-chars
|
[char]: #strings-and-chars
|
||||||
|
|
||||||
String and char literals follow C-style formatting, with support for Unicode ('`\u`_xxxx_' or '`\U`_xxxxxxxx_') and
|
String and character literals follow C-style formatting, with support for Unicode ('`\u`_xxxx_' or '`\U`_xxxxxxxx_')
|
||||||
hex ('`\x`_xx_') escape sequences.
|
and hex ('`\x`_xx_') escape sequences.
|
||||||
|
|
||||||
Hex sequences map to ASCII characters, while '`\u`' maps to 16-bit common Unicode code points and '`\U`' maps the full,
|
Hex sequences map to ASCII characters, while '`\u`' maps to 16-bit common Unicode code points and '`\U`' maps the full,
|
||||||
32-bit extended Unicode code points.
|
32-bit extended Unicode code points.
|
||||||
@ -1388,34 +1411,34 @@ record == "Bob X. Davis: age 42 ❤\n";
|
|||||||
|
|
||||||
### Built-in functions
|
### Built-in functions
|
||||||
|
|
||||||
The following standard methods (defined in the [`MoreStringPackage`](#packages) but excluded if using a [raw `Engine`]) operate on strings:
|
The following standard methods (mostly defined in the [`MoreStringPackage`](#packages) but excluded if using a [raw `Engine`]) operate on strings:
|
||||||
|
|
||||||
| Function | Parameter(s) | Description |
|
| Function | Parameter(s) | Description |
|
||||||
| ----------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
|
| ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
|
||||||
| `len` | _none_ | returns the number of characters (not number of bytes) in the string |
|
| `len` method and property | _none_ | returns the number of characters (not number of bytes) in the string |
|
||||||
| `pad` | character to pad, target length | pads the string with an character to at least a specified length |
|
| `pad` | character to pad, target length | pads the string with an character to at least a specified length |
|
||||||
| `+=` operator, `append` | character/string to append | Adds a character or a string to the end of another string |
|
| `+=` operator, `append` | character/string to append | Adds a character or a string to the end of another string |
|
||||||
| `clear` | _none_ | empties the string |
|
| `clear` | _none_ | empties the string |
|
||||||
| `truncate` | target length | cuts off the string at exactly a specified number of characters |
|
| `truncate` | target length | cuts off the string at exactly a specified number of characters |
|
||||||
| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string |
|
| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string |
|
||||||
| `index_of` | character/sub-string to search for, start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found |
|
| `index_of` | character/sub-string to search for, start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found |
|
||||||
| `sub_string` | start index, length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) |
|
| `sub_string` | start index, length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) |
|
||||||
| `crop` | start index, length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) |
|
| `crop` | start index, length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) |
|
||||||
| `replace` | target character/sub-string, replacement character/string | replaces a sub-string with another |
|
| `replace` | target character/sub-string, replacement character/string | replaces a sub-string with another |
|
||||||
| `trim` | _none_ | trims the string of whitespace at the beginning and end |
|
| `trim` | _none_ | trims the string of whitespace at the beginning and end |
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let full_name == " Bob C. Davis ";
|
let full_name == " Bob C. Davis ";
|
||||||
full_name.len() == 14;
|
full_name.len == 14;
|
||||||
|
|
||||||
full_name.trim();
|
full_name.trim();
|
||||||
full_name.len() == 12;
|
full_name.len == 12;
|
||||||
full_name == "Bob C. Davis";
|
full_name == "Bob C. Davis";
|
||||||
|
|
||||||
full_name.pad(15, '$');
|
full_name.pad(15, '$');
|
||||||
full_name.len() == 15;
|
full_name.len == 15;
|
||||||
full_name == "Bob C. Davis$$$";
|
full_name == "Bob C. Davis$$$";
|
||||||
|
|
||||||
let n = full_name.index_of('$');
|
let n = full_name.index_of('$');
|
||||||
@ -1426,11 +1449,11 @@ full_name.index_of("$$", n + 1) == 13;
|
|||||||
full_name.sub_string(n, 3) == "$$$";
|
full_name.sub_string(n, 3) == "$$$";
|
||||||
|
|
||||||
full_name.truncate(6);
|
full_name.truncate(6);
|
||||||
full_name.len() == 6;
|
full_name.len == 6;
|
||||||
full_name == "Bob C.";
|
full_name == "Bob C.";
|
||||||
|
|
||||||
full_name.replace("Bob", "John");
|
full_name.replace("Bob", "John");
|
||||||
full_name.len() == 7;
|
full_name.len == 7;
|
||||||
full_name == "John C.";
|
full_name == "John C.";
|
||||||
|
|
||||||
full_name.contains('C') == true;
|
full_name.contains('C') == true;
|
||||||
@ -1443,7 +1466,7 @@ full_name.crop(0, 1);
|
|||||||
full_name == "C";
|
full_name == "C";
|
||||||
|
|
||||||
full_name.clear();
|
full_name.clear();
|
||||||
full_name.len() == 0;
|
full_name.len == 0;
|
||||||
```
|
```
|
||||||
|
|
||||||
Arrays
|
Arrays
|
||||||
@ -1463,21 +1486,21 @@ Arrays are disabled via the [`no_index`] feature.
|
|||||||
|
|
||||||
### Built-in functions
|
### Built-in functions
|
||||||
|
|
||||||
The following methods (defined in the [`BasicArrayPackage`](#packages) but excluded if using a [raw `Engine`]) operate on arrays:
|
The following methods (mostly defined in the [`BasicArrayPackage`](#packages) but excluded if using a [raw `Engine`]) operate on arrays:
|
||||||
|
|
||||||
| Function | Parameter(s) | Description |
|
| Function | Parameter(s) | Description |
|
||||||
| ----------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
| ------------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||||
| `push` | element to insert | inserts an element at the end |
|
| `push` | element to insert | inserts an element at the end |
|
||||||
| `+=` operator, `append` | array to append | concatenates the second array to the end of the first |
|
| `+=` operator, `append` | 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 |
|
| `+` operator | first array, second array | concatenates the first array with the second |
|
||||||
| `insert` | element to insert, position<br/>(beginning if <= 0, end if >= length) | insert an element at a certain index |
|
| `insert` | element to insert, position<br/>(beginning if <= 0, end if >= length) | insert an element at a certain index |
|
||||||
| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) |
|
| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) |
|
||||||
| `shift` | _none_ | removes the first 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 |
|
| `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid |
|
||||||
| `len` | _none_ | returns the number of elements |
|
| `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 |
|
| `pad` | element to pad, target length | pads the array with an element to at least a specified length |
|
||||||
| `clear` | _none_ | empties the array |
|
| `clear` | _none_ | empties the array |
|
||||||
| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |
|
| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
@ -1487,7 +1510,7 @@ let y = [2, 3]; // array literal with 2 elements
|
|||||||
y.insert(0, 1); // insert element at the beginning
|
y.insert(0, 1); // insert element at the beginning
|
||||||
y.insert(999, 4); // insert element at the end
|
y.insert(999, 4); // insert element at the end
|
||||||
|
|
||||||
y.len() == 4;
|
y.len == 4;
|
||||||
|
|
||||||
y[0] == 1;
|
y[0] == 1;
|
||||||
y[1] == 2;
|
y[1] == 2;
|
||||||
@ -1504,7 +1527,7 @@ y[1] = 42; // array elements can be reassigned
|
|||||||
|
|
||||||
y.remove(2) == 3; // remove element
|
y.remove(2) == 3; // remove element
|
||||||
|
|
||||||
y.len() == 3;
|
y.len == 3;
|
||||||
|
|
||||||
y[2] == 4; // elements after the removed element are shifted
|
y[2] == 4; // elements after the removed element are shifted
|
||||||
|
|
||||||
@ -1528,7 +1551,7 @@ foo == 1;
|
|||||||
y.push(4); // 4 elements
|
y.push(4); // 4 elements
|
||||||
y.push(5); // 5 elements
|
y.push(5); // 5 elements
|
||||||
|
|
||||||
y.len() == 5;
|
y.len == 5;
|
||||||
|
|
||||||
let first = y.shift(); // remove the first element, 4 elements remaining
|
let first = y.shift(); // remove the first element, 4 elements remaining
|
||||||
first == 1;
|
first == 1;
|
||||||
@ -1536,7 +1559,7 @@ first == 1;
|
|||||||
let last = y.pop(); // remove the last element, 3 elements remaining
|
let last = y.pop(); // remove the last element, 3 elements remaining
|
||||||
last == 5;
|
last == 5;
|
||||||
|
|
||||||
y.len() == 3;
|
y.len == 3;
|
||||||
|
|
||||||
for item in y { // arrays can be iterated with a 'for' statement
|
for item in y { // arrays can be iterated with a 'for' statement
|
||||||
print(item);
|
print(item);
|
||||||
@ -1544,15 +1567,15 @@ for item in y { // arrays can be iterated with a 'for' statement
|
|||||||
|
|
||||||
y.pad(10, "hello"); // pad the array up to 10 elements
|
y.pad(10, "hello"); // pad the array up to 10 elements
|
||||||
|
|
||||||
y.len() == 10;
|
y.len == 10;
|
||||||
|
|
||||||
y.truncate(5); // truncate the array to 5 elements
|
y.truncate(5); // truncate the array to 5 elements
|
||||||
|
|
||||||
y.len() == 5;
|
y.len == 5;
|
||||||
|
|
||||||
y.clear(); // empty the array
|
y.clear(); // empty the array
|
||||||
|
|
||||||
y.len() == 0;
|
y.len == 0;
|
||||||
```
|
```
|
||||||
|
|
||||||
`push` and `pad` are only defined for standard built-in types. For custom types, type-specific versions must be registered:
|
`push` and `pad` are only defined for standard built-in types. For custom types, type-specific versions must be registered:
|
||||||
@ -1722,10 +1745,10 @@ The Rust type of a timestamp is `std::time::Instant`. [`type_of()`] a timestamp
|
|||||||
|
|
||||||
The following methods (defined in the [`BasicTimePackage`](#packages) but excluded if using a [raw `Engine`]) operate on timestamps:
|
The following methods (defined in the [`BasicTimePackage`](#packages) but excluded if using a [raw `Engine`]) operate on timestamps:
|
||||||
|
|
||||||
| Function | Parameter(s) | Description |
|
| Function | Parameter(s) | Description |
|
||||||
| ------------ | ---------------------------------- | -------------------------------------------------------- |
|
| ------------------ | ---------------------------------- | -------------------------------------------------------- |
|
||||||
| `elapsed` | _none_ | returns the number of seconds since the timestamp |
|
| `elapsed` property | _none_ | returns the number of seconds since the timestamp |
|
||||||
| `-` operator | later timestamp, earlier timestamp | returns the number of seconds between the two timestamps |
|
| `-` operator | later timestamp, earlier timestamp | returns the number of seconds between the two timestamps |
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
@ -1734,7 +1757,7 @@ let now = timestamp();
|
|||||||
|
|
||||||
// Do some lengthy operation...
|
// Do some lengthy operation...
|
||||||
|
|
||||||
if now.elapsed() > 30.0 {
|
if now.elapsed > 30.0 {
|
||||||
print("takes too long (over 30 seconds)!")
|
print("takes too long (over 30 seconds)!")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -1963,6 +1986,9 @@ println!(result); // prints "Runtime error: 42 is too large! (line 5,
|
|||||||
Functions
|
Functions
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
[function]: #functions
|
||||||
|
[functions]: #functions
|
||||||
|
|
||||||
Rhai supports defining functions in script (unless disabled with [`no_function`]):
|
Rhai supports defining functions in script (unless disabled with [`no_function`]):
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@ -2007,8 +2033,9 @@ fn foo() { x } // <- syntax error: variable 'x' doesn't exist
|
|||||||
|
|
||||||
Functions defined in script always take [`Dynamic`] parameters (i.e. the parameter can be of any type).
|
Functions defined in script always take [`Dynamic`] parameters (i.e. the parameter can be of any type).
|
||||||
It is important to remember that all arguments are passed by _value_, so all functions are _pure_
|
It is important to remember that all arguments are passed by _value_, so all functions are _pure_
|
||||||
(i.e. they never modifytheir arguments).
|
(i.e. they never modify their arguments).
|
||||||
Any update to an argument will **not** be reflected back to the caller. This can introduce subtle bugs, if not careful.
|
Any update to an argument will **not** be reflected back to the caller.
|
||||||
|
This can introduce subtle bugs, if not careful, especially when using the _method-call_ style.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn change(s) { // 's' is passed by value
|
fn change(s) { // 's' is passed by value
|
||||||
@ -2016,7 +2043,7 @@ fn change(s) { // 's' is passed by value
|
|||||||
}
|
}
|
||||||
|
|
||||||
let x = 500;
|
let x = 500;
|
||||||
x.change(); // de-sugars to change(x)
|
x.change(); // de-sugars to 'change(x)'
|
||||||
x == 500; // 'x' is NOT changed!
|
x == 500; // 'x' is NOT changed!
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -2067,14 +2094,24 @@ Members and methods
|
|||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
Properties and methods in a Rust custom type registered with the [`Engine`] can be called just like in Rust.
|
Properties and methods in a Rust custom type registered with the [`Engine`] can be called just like in Rust.
|
||||||
|
Unlike functions defined in script (for which all arguments are passed by _value_),
|
||||||
|
native Rust functions may mutate the object (or the first argument if called in normal function call style).
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let a = new_ts(); // constructor function
|
let a = new_ts(); // constructor function
|
||||||
a.field = 500; // property access
|
a.field = 500; // property setter
|
||||||
a.update(); // method call, 'a' can be changed
|
a.update(); // method call, 'a' can be modified
|
||||||
|
|
||||||
update(a); // this works, but 'a' is unchanged because only
|
update(a); // <- this de-sugars to 'a.update()' this if 'a' is a simple variable
|
||||||
// a COPY of 'a' is passed to 'update' by VALUE
|
// unlike scripted functions, 'a' can be modified and is not a copy
|
||||||
|
|
||||||
|
let array = [ a ];
|
||||||
|
|
||||||
|
update(array[0]); // <- 'array[0]' is an expression returning a calculated value,
|
||||||
|
// a transient (i.e. a copy) so this statement has no effect
|
||||||
|
// except waste a lot of time cloning
|
||||||
|
|
||||||
|
array[0].update(); // <- call this method-call style will update 'a'
|
||||||
```
|
```
|
||||||
|
|
||||||
Custom types, properties and methods can be disabled via the [`no_object`] feature.
|
Custom types, properties and methods can be disabled via the [`no_object`] feature.
|
||||||
@ -2244,7 +2281,7 @@ let ast = engine.compile(r#"
|
|||||||
x + 1
|
x + 1
|
||||||
}
|
}
|
||||||
fn add_len(x, y) {
|
fn add_len(x, y) {
|
||||||
x + y.len()
|
x + y.len
|
||||||
}
|
}
|
||||||
|
|
||||||
// Imported modules can become sub-modules
|
// Imported modules can become sub-modules
|
||||||
|
19
RELEASES.md
19
RELEASES.md
@ -9,6 +9,12 @@ Regression fix
|
|||||||
|
|
||||||
* Do not optimize script with `eval_expression` - it is assumed to be one-off and short.
|
* Do not optimize script with `eval_expression` - it is assumed to be one-off and short.
|
||||||
|
|
||||||
|
Bug fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
* Indexing with an index or dot expression now works property (it compiled wrongly before).
|
||||||
|
For example, `let s = "hello"; s[s.len-1] = 'x';` now works property instead of an error.
|
||||||
|
|
||||||
Breaking changes
|
Breaking changes
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
@ -22,13 +28,19 @@ Breaking changes
|
|||||||
This is to avoid excessive cloning of strings. All native-Rust functions taking string parameters
|
This is to avoid excessive cloning of strings. All native-Rust functions taking string parameters
|
||||||
should switch to `rhai::ImmutableString` (which is either `Rc<String>` or `Arc<String>` depending on
|
should switch to `rhai::ImmutableString` (which is either `Rc<String>` or `Arc<String>` depending on
|
||||||
whether the `sync` feature is used).
|
whether the `sync` feature is used).
|
||||||
|
* Native Rust functions registered with the `Engine` also mutates the first argument when called in
|
||||||
|
normal function-call style (previously the first argument will be passed by _value_ if not called
|
||||||
|
in method-call style). Of course, if the first argument is a calculated value (e.g. result of an
|
||||||
|
expression), then mutating it has no effect, but at least it is not cloned.
|
||||||
|
* Some built-in methods (e.g. `len` for string, `floor` for `FLOAT`) now have _property_ versions in
|
||||||
|
addition to methods to simplify coding.
|
||||||
|
|
||||||
New features
|
New features
|
||||||
------------
|
------------
|
||||||
|
|
||||||
* Set limit on maximum level of nesting expressions and statements to avoid panics during parsing.
|
* Set limit on maximum level of nesting expressions and statements to avoid panics during parsing.
|
||||||
* New `EvalPackage` to disable `eval`.
|
* New `EvalPackage` to disable `eval`.
|
||||||
* More benchmarks.
|
* `Module::set_getter_fn`, `Module::set_setter_fn` and `Module:set_indexer_fn` to register getter/setter/indexer functions.
|
||||||
|
|
||||||
Speed enhancements
|
Speed enhancements
|
||||||
------------------
|
------------------
|
||||||
@ -43,6 +55,11 @@ Speed enhancements
|
|||||||
(and therefore the `CorePackage`) because they are now always available, even under `Engine::new_raw`.
|
(and therefore the `CorePackage`) because they are now always available, even under `Engine::new_raw`.
|
||||||
* Operator-assignment statements (e.g. `+=`) are now handled directly and much faster.
|
* Operator-assignment statements (e.g. `+=`) are now handled directly and much faster.
|
||||||
* Strings are now _immutable_ and use the `rhai::ImmutableString` type, eliminating large amounts of cloning.
|
* Strings are now _immutable_ and use the `rhai::ImmutableString` type, eliminating large amounts of cloning.
|
||||||
|
* For Native Rust functions taking a first `&mut` parameter, the first argument is passed by reference instead of
|
||||||
|
by value, even if not called in method-call style. This allows many functions declared with `&mut` parameter to avoid
|
||||||
|
excessive cloning. For example, if `a` is a large array, getting its length in this manner: `len(a)` used to result
|
||||||
|
in a full clone of `a` before taking the length and throwing the copy away. Now, `a` is simply passed by reference,
|
||||||
|
avoiding the cloning altogether.
|
||||||
|
|
||||||
|
|
||||||
Version 0.14.1
|
Version 0.14.1
|
||||||
|
@ -48,7 +48,7 @@ fn bench_eval_expression_optimized_simple(bench: &mut Bencher) {
|
|||||||
2 > 1 &&
|
2 > 1 &&
|
||||||
"something" != "nothing" ||
|
"something" != "nothing" ||
|
||||||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
||||||
[array, with, spaces].len() <= #{prop:name}.len() &&
|
[array, with, spaces].len <= #{prop:name}.len &&
|
||||||
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ fn bench_eval_expression_optimized_full(bench: &mut Bencher) {
|
|||||||
2 > 1 &&
|
2 > 1 &&
|
||||||
"something" != "nothing" ||
|
"something" != "nothing" ||
|
||||||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
||||||
[array, with, spaces].len() <= #{prop:name}.len() &&
|
[array, with, spaces].len <= #{prop:name}.len &&
|
||||||
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ fn bench_eval_call_expression(bench: &mut Bencher) {
|
|||||||
2 > 1 &&
|
2 > 1 &&
|
||||||
"something" != "nothing" ||
|
"something" != "nothing" ||
|
||||||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
||||||
[array, with, spaces].len() <= #{prop:name}.len() &&
|
[array, with, spaces].len <= #{prop:name}.len &&
|
||||||
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ fn bench_eval_call(bench: &mut Bencher) {
|
|||||||
2 > 1 &&
|
2 > 1 &&
|
||||||
"something" != "nothing" ||
|
"something" != "nothing" ||
|
||||||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
||||||
[array, with, spaces].len() <= #{prop:name}.len() &&
|
[array, with, spaces].len <= #{prop:name}.len &&
|
||||||
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ fn bench_parse_full(bench: &mut Bencher) {
|
|||||||
2 > 1 &&
|
2 > 1 &&
|
||||||
"something" != "nothing" ||
|
"something" != "nothing" ||
|
||||||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
||||||
[array, with, spaces].len() <= #{prop:name}.len() &&
|
[array, with, spaces].len <= #{prop:name}.len &&
|
||||||
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ fn bench_parse_primes(bench: &mut Bencher) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
print("Total " + total_primes_found + " primes.");
|
print("Total " + total_primes_found + " primes.");
|
||||||
print("Run time = " + now.elapsed() + " seconds.");
|
print("Run time = " + now.elapsed + " seconds.");
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
@ -109,7 +109,7 @@ fn bench_parse_optimize_simple(bench: &mut Bencher) {
|
|||||||
2 > 1 &&
|
2 > 1 &&
|
||||||
"something" != "nothing" ||
|
"something" != "nothing" ||
|
||||||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
||||||
[array, with, spaces].len() <= #{prop:name}.len() &&
|
[array, with, spaces].len <= #{prop:name}.len &&
|
||||||
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ fn bench_parse_optimize_full(bench: &mut Bencher) {
|
|||||||
2 > 1 &&
|
2 > 1 &&
|
||||||
"something" != "nothing" ||
|
"something" != "nothing" ||
|
||||||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
||||||
[array, with, spaces].len() <= #{prop:name}.len() &&
|
[array, with, spaces].len <= #{prop:name}.len &&
|
||||||
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ let now = timestamp();
|
|||||||
|
|
||||||
let result = fib(target);
|
let result = fib(target);
|
||||||
|
|
||||||
print("Finished. Run time = " + now.elapsed() + " seconds.");
|
print("Finished. Run time = " + now.elapsed + " seconds.");
|
||||||
|
|
||||||
print("Fibonacci number #" + target + " = " + result);
|
print("Fibonacci number #" + target + " = " + result);
|
||||||
|
|
||||||
|
@ -24,9 +24,9 @@ fn mat_gen(n) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn mat_mul(a, b) {
|
fn mat_mul(a, b) {
|
||||||
let m = a.len();
|
let m = a.len;
|
||||||
let n = a[0].len();
|
let n = a[0].len;
|
||||||
let p = b[0].len();
|
let p = b[0].len;
|
||||||
|
|
||||||
let b2 = new_mat(n, p);
|
let b2 = new_mat(n, p);
|
||||||
|
|
||||||
@ -38,13 +38,13 @@ fn mat_mul(a, b) {
|
|||||||
|
|
||||||
let c = new_mat(m, p);
|
let c = new_mat(m, p);
|
||||||
|
|
||||||
for i in range(0, c.len()) {
|
for i in range(0, c.len) {
|
||||||
let ci = c[i];
|
let ci = c[i];
|
||||||
for j in range(0, ci.len()) {
|
for j in range(0, ci.len) {
|
||||||
let b2j = b2[j];
|
let b2j = b2[j];
|
||||||
ci[j] = 0.0;
|
ci[j] = 0.0;
|
||||||
|
|
||||||
for z in range(0, a[i].len()) {
|
for z in range(0, a[i].len) {
|
||||||
let x = a[i][z];
|
let x = a[i][z];
|
||||||
let y = b2j[z];
|
let y = b2j[z];
|
||||||
ci[j] += x * y;
|
ci[j] += x * y;
|
||||||
@ -66,4 +66,4 @@ for i in range(0, SIZE) {
|
|||||||
print(c[i]);
|
print(c[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
print("Finished. Run time = " + now.elapsed() + " seconds.");
|
print("Finished. Run time = " + now.elapsed + " seconds.");
|
||||||
|
@ -26,7 +26,7 @@ for p in range(2, MAX_NUMBER_TO_CHECK) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
print("Total " + total_primes_found + " primes <= " + MAX_NUMBER_TO_CHECK);
|
print("Total " + total_primes_found + " primes <= " + MAX_NUMBER_TO_CHECK);
|
||||||
print("Run time = " + now.elapsed() + " seconds.");
|
print("Run time = " + now.elapsed + " seconds.");
|
||||||
|
|
||||||
if total_primes_found != 9_592 {
|
if total_primes_found != 9_592 {
|
||||||
print("The answer is WRONG! Should be 9,592!");
|
print("The answer is WRONG! Should be 9,592!");
|
||||||
|
@ -10,4 +10,4 @@ while x > 0 {
|
|||||||
x -= 1;
|
x -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
print("Finished. Run time = " + now.elapsed() + " seconds.");
|
print("Finished. Run time = " + now.elapsed + " seconds.");
|
||||||
|
@ -11,7 +11,7 @@ print("foo" >= "bar"); // string comparison
|
|||||||
print("the answer is " + 42); // string building using non-string types
|
print("the answer is " + 42); // string building using non-string types
|
||||||
|
|
||||||
let s = "hello, world!"; // string variable
|
let s = "hello, world!"; // string variable
|
||||||
print("length=" + s.len()); // should be 13
|
print("length=" + s.len); // should be 13
|
||||||
|
|
||||||
s[s.len()-1] = '?'; // change the string
|
s[s.len-1] = '?'; // change the string
|
||||||
print(s); // should print 'hello, world?'
|
print("Question: " + s); // should print 'Question: hello, world?'
|
||||||
|
@ -630,6 +630,14 @@ impl<T: Variant + Clone> From<Vec<T>> for Dynamic {
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
impl<T: Variant + Clone> From<&[T]> for Dynamic {
|
||||||
|
fn from(value: &[T]) -> Self {
|
||||||
|
Self(Union::Array(Box::new(
|
||||||
|
value.iter().cloned().map(Dynamic::from).collect(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
impl<T: Variant + Clone> From<HashMap<String, T>> for Dynamic {
|
impl<T: Variant + Clone> From<HashMap<String, T>> for Dynamic {
|
||||||
fn from(value: HashMap<String, T>) -> Self {
|
fn from(value: HashMap<String, T>) -> Self {
|
||||||
|
@ -1051,8 +1051,7 @@ impl Engine {
|
|||||||
let mut state = State::new();
|
let mut state = State::new();
|
||||||
let args = args.as_mut();
|
let args = args.as_mut();
|
||||||
|
|
||||||
let result =
|
let result = self.call_script_fn(scope, &mut state, &lib, name, fn_def, args, pos, 0)?;
|
||||||
self.call_script_fn(Some(scope), &mut state, &lib, name, fn_def, args, pos, 0)?;
|
|
||||||
|
|
||||||
let return_type = self.map_type_name(result.type_name());
|
let return_type = self.map_type_name(result.type_name());
|
||||||
|
|
||||||
|
300
src/engine.rs
300
src/engine.rs
@ -432,11 +432,11 @@ fn default_print(s: &str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Search for a variable within the scope
|
/// Search for a variable within the scope
|
||||||
fn search_scope<'a>(
|
fn search_scope<'s, 'a>(
|
||||||
scope: &'a mut Scope,
|
scope: &'s mut Scope,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
expr: &'a Expr,
|
expr: &'a Expr,
|
||||||
) -> Result<(&'a mut Dynamic, &'a str, ScopeEntryType, Position), Box<EvalAltResult>> {
|
) -> Result<(&'s mut Dynamic, &'a str, ScopeEntryType, Position), Box<EvalAltResult>> {
|
||||||
let ((name, pos), modules, hash_var, index) = match expr {
|
let ((name, pos), modules, hash_var, index) = match expr {
|
||||||
Expr::Variable(x) => x.as_ref(),
|
Expr::Variable(x) => x.as_ref(),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
@ -592,7 +592,7 @@ impl Engine {
|
|||||||
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
|
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
|
||||||
pub(crate) fn call_fn_raw(
|
pub(crate) fn call_fn_raw(
|
||||||
&self,
|
&self,
|
||||||
scope: Option<&mut Scope>,
|
scope: &mut Scope,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &FunctionsLib,
|
lib: &FunctionsLib,
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
@ -615,11 +615,55 @@ impl Engine {
|
|||||||
return Err(Box::new(EvalAltResult::ErrorStackOverflow(pos)));
|
return Err(Box::new(EvalAltResult::ErrorStackOverflow(pos)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut this_copy: Dynamic = Default::default();
|
||||||
|
let mut old_this_ptr: Option<&mut Dynamic> = None;
|
||||||
|
|
||||||
|
/// This function replaces the first argument of a method call with a clone copy.
|
||||||
|
/// This is to prevent a pure function unintentionally consuming the first argument.
|
||||||
|
fn normalize_first_arg<'a>(
|
||||||
|
normalize: bool,
|
||||||
|
this_copy: &mut Dynamic,
|
||||||
|
old_this_ptr: &mut Option<&'a mut Dynamic>,
|
||||||
|
args: &mut FnCallArgs<'a>,
|
||||||
|
) {
|
||||||
|
// Only do it for method calls with arguments.
|
||||||
|
if !normalize || args.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone the original value.
|
||||||
|
*this_copy = args[0].clone();
|
||||||
|
|
||||||
|
// Replace the first reference with a reference to the clone, force-casting the lifetime.
|
||||||
|
// Keep the original reference. Must remember to restore it later with `restore_first_arg_of_method_call`.
|
||||||
|
let this_pointer = mem::replace(
|
||||||
|
args.get_mut(0).unwrap(),
|
||||||
|
unsafe_mut_cast_to_lifetime(this_copy),
|
||||||
|
);
|
||||||
|
|
||||||
|
*old_this_ptr = Some(this_pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function restores the first argument that was replaced by `normalize_first_arg_of_method_call`.
|
||||||
|
fn restore_first_arg<'a>(old_this_ptr: Option<&'a mut Dynamic>, args: &mut FnCallArgs<'a>) {
|
||||||
|
if let Some(this_pointer) = old_this_ptr {
|
||||||
|
mem::replace(args.get_mut(0).unwrap(), this_pointer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// First search in script-defined functions (can override built-in)
|
// First search in script-defined functions (can override built-in)
|
||||||
if !native_only {
|
if !native_only {
|
||||||
if let Some(fn_def) = lib.get(&hashes.1) {
|
if let Some(fn_def) = lib.get(&hashes.1) {
|
||||||
|
normalize_first_arg(is_ref, &mut this_copy, &mut old_this_ptr, args);
|
||||||
|
|
||||||
|
// Run scripted function
|
||||||
let result =
|
let result =
|
||||||
self.call_script_fn(scope, state, lib, fn_name, fn_def, args, pos, level)?;
|
self.call_script_fn(scope, state, lib, fn_name, fn_def, args, pos, level)?;
|
||||||
|
|
||||||
|
// Restore the original reference
|
||||||
|
restore_first_arg(old_this_ptr, args);
|
||||||
|
|
||||||
return Ok((result, false));
|
return Ok((result, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -631,29 +675,18 @@ impl Engine {
|
|||||||
.or_else(|| self.packages.get_fn(hashes.0))
|
.or_else(|| self.packages.get_fn(hashes.0))
|
||||||
{
|
{
|
||||||
// Calling pure function in method-call?
|
// Calling pure function in method-call?
|
||||||
let mut this_copy: Option<Dynamic>;
|
normalize_first_arg(
|
||||||
let mut this_pointer: Option<&mut Dynamic> = None;
|
func.is_pure() && is_ref,
|
||||||
|
&mut this_copy,
|
||||||
if func.is_pure() && is_ref && !args.is_empty() {
|
&mut old_this_ptr,
|
||||||
// Clone the original value. It'll be consumed because the function
|
args,
|
||||||
// is pure and doesn't know that the first value is a reference (i.e. `is_ref`)
|
);
|
||||||
this_copy = Some(args[0].clone());
|
|
||||||
|
|
||||||
// Replace the first reference with a reference to the clone, force-casting the lifetime.
|
|
||||||
// Keep the original reference. Must remember to restore it before existing this function.
|
|
||||||
this_pointer = Some(mem::replace(
|
|
||||||
args.get_mut(0).unwrap(),
|
|
||||||
unsafe_mut_cast_to_lifetime(this_copy.as_mut().unwrap()),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run external function
|
// Run external function
|
||||||
let result = func.get_native_fn()(args);
|
let result = func.get_native_fn()(args);
|
||||||
|
|
||||||
// Restore the original reference
|
// Restore the original reference
|
||||||
if let Some(this_pointer) = this_pointer {
|
restore_first_arg(old_this_ptr, args);
|
||||||
mem::replace(args.get_mut(0).unwrap(), this_pointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = result.map_err(|err| err.new_position(pos))?;
|
let result = result.map_err(|err| err.new_position(pos))?;
|
||||||
|
|
||||||
@ -741,7 +774,7 @@ impl Engine {
|
|||||||
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
|
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
|
||||||
pub(crate) fn call_script_fn(
|
pub(crate) fn call_script_fn(
|
||||||
&self,
|
&self,
|
||||||
scope: Option<&mut Scope>,
|
scope: &mut Scope,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &FunctionsLib,
|
lib: &FunctionsLib,
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
@ -753,88 +786,42 @@ impl Engine {
|
|||||||
let orig_scope_level = state.scope_level;
|
let orig_scope_level = state.scope_level;
|
||||||
state.scope_level += 1;
|
state.scope_level += 1;
|
||||||
|
|
||||||
match scope {
|
let scope_len = scope.len();
|
||||||
// Extern scope passed in which is not empty
|
|
||||||
Some(scope) if !scope.is_empty() => {
|
|
||||||
let scope_len = scope.len();
|
|
||||||
|
|
||||||
// Put arguments into scope as variables
|
// Put arguments into scope as variables
|
||||||
// Actually consume the arguments instead of cloning them
|
// Actually consume the arguments instead of cloning them
|
||||||
scope.extend(
|
scope.extend(
|
||||||
fn_def
|
fn_def
|
||||||
.params
|
.params
|
||||||
.iter()
|
.iter()
|
||||||
.zip(args.iter_mut().map(|v| mem::take(*v)))
|
.zip(args.iter_mut().map(|v| mem::take(*v)))
|
||||||
.map(|(name, value)| {
|
.map(|(name, value)| {
|
||||||
let var_name = unsafe_cast_var_name_to_lifetime(name.as_str(), state);
|
let var_name = unsafe_cast_var_name_to_lifetime(name.as_str(), state);
|
||||||
(var_name, ScopeEntryType::Normal, value)
|
(var_name, ScopeEntryType::Normal, value)
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Evaluate the function at one higher level of call depth
|
// Evaluate the function at one higher level of call depth
|
||||||
let result = self
|
let result = self
|
||||||
.eval_stmt(scope, state, lib, &fn_def.body, level + 1)
|
.eval_stmt(scope, state, lib, &fn_def.body, level + 1)
|
||||||
.or_else(|err| match *err {
|
.or_else(|err| match *err {
|
||||||
// Convert return statement to return value
|
// Convert return statement to return value
|
||||||
EvalAltResult::Return(x, _) => Ok(x),
|
EvalAltResult::Return(x, _) => Ok(x),
|
||||||
EvalAltResult::ErrorInFunctionCall(name, err, _) => {
|
EvalAltResult::ErrorInFunctionCall(name, err, _) => Err(Box::new(
|
||||||
Err(Box::new(EvalAltResult::ErrorInFunctionCall(
|
EvalAltResult::ErrorInFunctionCall(format!("{} > {}", fn_name, name), err, pos),
|
||||||
format!("{} > {}", fn_name, name),
|
)),
|
||||||
err,
|
_ => Err(Box::new(EvalAltResult::ErrorInFunctionCall(
|
||||||
pos,
|
fn_name.to_string(),
|
||||||
)))
|
err,
|
||||||
}
|
pos,
|
||||||
_ => Err(Box::new(EvalAltResult::ErrorInFunctionCall(
|
))),
|
||||||
fn_name.to_string(),
|
});
|
||||||
err,
|
|
||||||
pos,
|
|
||||||
))),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove all local variables
|
// Remove all local variables
|
||||||
scope.rewind(scope_len);
|
scope.rewind(scope_len);
|
||||||
state.scope_level = orig_scope_level;
|
state.scope_level = orig_scope_level;
|
||||||
|
|
||||||
return result;
|
result
|
||||||
}
|
|
||||||
// No new scope - create internal scope
|
|
||||||
_ => {
|
|
||||||
let mut scope = Scope::new();
|
|
||||||
|
|
||||||
// Put arguments into scope as variables
|
|
||||||
// Actually consume the arguments instead of cloning them
|
|
||||||
scope.extend(
|
|
||||||
fn_def
|
|
||||||
.params
|
|
||||||
.iter()
|
|
||||||
.zip(args.iter_mut().map(|v| mem::take(*v)))
|
|
||||||
.map(|(name, value)| (name, ScopeEntryType::Normal, value)),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Evaluate the function at one higher level of call depth
|
|
||||||
let result = self
|
|
||||||
.eval_stmt(&mut scope, state, lib, &fn_def.body, level + 1)
|
|
||||||
.or_else(|err| match *err {
|
|
||||||
// Convert return statement to return value
|
|
||||||
EvalAltResult::Return(x, _) => Ok(x),
|
|
||||||
EvalAltResult::ErrorInFunctionCall(name, err, _) => {
|
|
||||||
Err(Box::new(EvalAltResult::ErrorInFunctionCall(
|
|
||||||
format!("{} > {}", fn_name, name),
|
|
||||||
err,
|
|
||||||
pos,
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
_ => Err(Box::new(EvalAltResult::ErrorInFunctionCall(
|
|
||||||
fn_name.to_string(),
|
|
||||||
err,
|
|
||||||
pos,
|
|
||||||
))),
|
|
||||||
});
|
|
||||||
|
|
||||||
state.scope_level = orig_scope_level;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Has a system function an override?
|
// Has a system function an override?
|
||||||
@ -892,9 +879,12 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Normal function call
|
// Normal function call
|
||||||
_ => self.call_fn_raw(
|
_ => {
|
||||||
None, state, lib, fn_name, hashes, args, is_ref, def_val, pos, level,
|
let mut scope = Scope::new();
|
||||||
),
|
self.call_fn_raw(
|
||||||
|
&mut scope, state, lib, fn_name, hashes, args, is_ref, def_val, pos, level,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1219,14 +1209,16 @@ impl Engine {
|
|||||||
Expr::FnCall(_) => unreachable!(),
|
Expr::FnCall(_) => unreachable!(),
|
||||||
Expr::Property(_) => idx_values.push(()), // Store a placeholder - no need to copy the property name
|
Expr::Property(_) => idx_values.push(()), // Store a placeholder - no need to copy the property name
|
||||||
Expr::Index(x) | Expr::Dot(x) => {
|
Expr::Index(x) | Expr::Dot(x) => {
|
||||||
|
let (lhs, rhs, _) = x.as_ref();
|
||||||
|
|
||||||
// Evaluate in left-to-right order
|
// Evaluate in left-to-right order
|
||||||
let lhs_val = match x.0 {
|
let lhs_val = match lhs {
|
||||||
Expr::Property(_) => Default::default(), // Store a placeholder in case of a property
|
Expr::Property(_) => Default::default(), // Store a placeholder in case of a property
|
||||||
_ => self.eval_expr(scope, state, lib, &x.0, level)?,
|
_ => self.eval_expr(scope, state, lib, lhs, level)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Push in reverse order
|
// Push in reverse order
|
||||||
self.eval_indexed_chain(scope, state, lib, &x.1, idx_values, size, level)?;
|
self.eval_indexed_chain(scope, state, lib, rhs, idx_values, size, level)?;
|
||||||
|
|
||||||
idx_values.push(lhs_val);
|
idx_values.push(lhs_val);
|
||||||
}
|
}
|
||||||
@ -1347,6 +1339,7 @@ impl Engine {
|
|||||||
Dynamic(Union::Array(mut rhs_value)) => {
|
Dynamic(Union::Array(mut rhs_value)) => {
|
||||||
let op = "==";
|
let op = "==";
|
||||||
let def_value = false.into();
|
let def_value = false.into();
|
||||||
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
// Call the `==` operator to compare each value
|
// Call the `==` operator to compare each value
|
||||||
for value in rhs_value.iter_mut() {
|
for value in rhs_value.iter_mut() {
|
||||||
@ -1361,7 +1354,7 @@ impl Engine {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let (r, _) = self.call_fn_raw(
|
let (r, _) = self.call_fn_raw(
|
||||||
None, state, lib, op, hashes, args, false, def_value, pos, level,
|
&mut scope, state, lib, op, hashes, args, false, def_value, pos, level,
|
||||||
)?;
|
)?;
|
||||||
if r.as_bool().unwrap_or(false) {
|
if r.as_bool().unwrap_or(false) {
|
||||||
return Ok(true.into());
|
return Ok(true.into());
|
||||||
@ -1399,6 +1392,8 @@ impl Engine {
|
|||||||
self.inc_operations(state, expr.position())?;
|
self.inc_operations(state, expr.position())?;
|
||||||
|
|
||||||
match expr {
|
match expr {
|
||||||
|
Expr::Expr(x) => self.eval_expr(scope, state, lib, x.as_ref(), level),
|
||||||
|
|
||||||
Expr::IntegerConstant(x) => Ok(x.0.into()),
|
Expr::IntegerConstant(x) => Ok(x.0.into()),
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Expr::FloatConstant(x) => Ok(x.0.into()),
|
Expr::FloatConstant(x) => Ok(x.0.into()),
|
||||||
@ -1550,14 +1545,8 @@ impl Engine {
|
|||||||
let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref();
|
let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref();
|
||||||
let def_val = def_val.as_ref();
|
let def_val = def_val.as_ref();
|
||||||
|
|
||||||
let mut arg_values = args_expr
|
// Handle eval
|
||||||
.iter()
|
if name == KEYWORD_EVAL && args_expr.len() == 1 {
|
||||||
.map(|expr| self.eval_expr(scope, state, lib, expr, level))
|
|
||||||
.collect::<Result<StaticVec<_>, _>>()?;
|
|
||||||
|
|
||||||
let mut args: StaticVec<_> = arg_values.iter_mut().collect();
|
|
||||||
|
|
||||||
if name == KEYWORD_EVAL && args.len() == 1 && args.get(0).is::<ImmutableString>() {
|
|
||||||
let hash_fn =
|
let hash_fn =
|
||||||
calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
|
calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
|
||||||
|
|
||||||
@ -1567,7 +1556,8 @@ impl Engine {
|
|||||||
let pos = args_expr.get(0).position();
|
let pos = args_expr.get(0).position();
|
||||||
|
|
||||||
// Evaluate the text string as a script
|
// Evaluate the text string as a script
|
||||||
let result = self.eval_script_expr(scope, state, lib, args.pop(), pos);
|
let script = self.eval_expr(scope, state, lib, args_expr.get(0), level)?;
|
||||||
|
let result = self.eval_script_expr(scope, state, lib, &script, pos);
|
||||||
|
|
||||||
if scope.len() != prev_len {
|
if scope.len() != prev_len {
|
||||||
// IMPORTANT! If the eval defines new variables in the current scope,
|
// IMPORTANT! If the eval defines new variables in the current scope,
|
||||||
@ -1580,9 +1570,52 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Normal function call - except for eval (handled above)
|
// Normal function call - except for eval (handled above)
|
||||||
|
let mut arg_values: StaticVec<Dynamic>;
|
||||||
|
let mut args: StaticVec<_>;
|
||||||
|
let mut is_ref = false;
|
||||||
|
|
||||||
|
if args_expr.is_empty() {
|
||||||
|
// No arguments
|
||||||
|
args = Default::default();
|
||||||
|
} else {
|
||||||
|
// See if the first argument is a variable, if so, convert to method-call style
|
||||||
|
// in order to leverage potential &mut first argument and avoid cloning the value
|
||||||
|
match args_expr.get(0) {
|
||||||
|
// func(x, ...) -> x.func(...)
|
||||||
|
lhs @ Expr::Variable(_) => {
|
||||||
|
arg_values = args_expr
|
||||||
|
.iter()
|
||||||
|
.skip(1)
|
||||||
|
.map(|expr| self.eval_expr(scope, state, lib, expr, level))
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
|
|
||||||
|
let (target, _, typ, pos) = search_scope(scope, state, lhs)?;
|
||||||
|
self.inc_operations(state, pos)?;
|
||||||
|
|
||||||
|
match typ {
|
||||||
|
ScopeEntryType::Module => unreachable!(),
|
||||||
|
ScopeEntryType::Constant | ScopeEntryType::Normal => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
args = once(target).chain(arg_values.iter_mut()).collect();
|
||||||
|
|
||||||
|
is_ref = true;
|
||||||
|
}
|
||||||
|
// func(..., ...)
|
||||||
|
_ => {
|
||||||
|
arg_values = args_expr
|
||||||
|
.iter()
|
||||||
|
.map(|expr| self.eval_expr(scope, state, lib, expr, level))
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
|
|
||||||
|
args = arg_values.iter_mut().collect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let args = args.as_mut();
|
let args = args.as_mut();
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
state, lib, name, *native, *hash, args, false, def_val, *pos, level,
|
state, lib, name, *native, *hash, args, is_ref, def_val, *pos, level,
|
||||||
)
|
)
|
||||||
.map(|(v, _)| v)
|
.map(|(v, _)| v)
|
||||||
}
|
}
|
||||||
@ -1639,9 +1672,8 @@ impl Engine {
|
|||||||
Ok(x) if x.is_script() => {
|
Ok(x) if x.is_script() => {
|
||||||
let args = args.as_mut();
|
let args = args.as_mut();
|
||||||
let fn_def = x.get_fn_def();
|
let fn_def = x.get_fn_def();
|
||||||
let result =
|
let mut scope = Scope::new();
|
||||||
self.call_script_fn(None, state, lib, name, fn_def, args, *pos, level)?;
|
self.call_script_fn(&mut scope, state, lib, name, fn_def, args, *pos, level)
|
||||||
Ok(result)
|
|
||||||
}
|
}
|
||||||
Ok(x) => x.get_native_fn()(args.as_mut()).map_err(|err| err.new_position(*pos)),
|
Ok(x) => x.get_native_fn()(args.as_mut()).map_err(|err| err.new_position(*pos)),
|
||||||
Err(err)
|
Err(err)
|
||||||
@ -1701,9 +1733,9 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a statement
|
/// Evaluate a statement
|
||||||
pub(crate) fn eval_stmt<'s>(
|
pub(crate) fn eval_stmt(
|
||||||
&self,
|
&self,
|
||||||
scope: &mut Scope<'s>,
|
scope: &mut Scope,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &FunctionsLib,
|
lib: &FunctionsLib,
|
||||||
stmt: &Stmt,
|
stmt: &Stmt,
|
||||||
@ -2012,8 +2044,8 @@ fn run_builtin_binary_op(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if args_type == TypeId::of::<INT>() {
|
if args_type == TypeId::of::<INT>() {
|
||||||
let x = x.downcast_ref::<INT>().unwrap().clone();
|
let x = *x.downcast_ref::<INT>().unwrap();
|
||||||
let y = y.downcast_ref::<INT>().unwrap().clone();
|
let y = *y.downcast_ref::<INT>().unwrap();
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
match op {
|
match op {
|
||||||
@ -2054,8 +2086,8 @@ fn run_builtin_binary_op(
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
} else if args_type == TypeId::of::<bool>() {
|
} else if args_type == TypeId::of::<bool>() {
|
||||||
let x = x.downcast_ref::<bool>().unwrap().clone();
|
let x = *x.downcast_ref::<bool>().unwrap();
|
||||||
let y = y.downcast_ref::<bool>().unwrap().clone();
|
let y = *y.downcast_ref::<bool>().unwrap();
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
"&" => return Ok(Some((x && y).into())),
|
"&" => return Ok(Some((x && y).into())),
|
||||||
@ -2079,8 +2111,8 @@ fn run_builtin_binary_op(
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
} else if args_type == TypeId::of::<char>() {
|
} else if args_type == TypeId::of::<char>() {
|
||||||
let x = x.downcast_ref::<char>().unwrap().clone();
|
let x = *x.downcast_ref::<char>().unwrap();
|
||||||
let y = y.downcast_ref::<char>().unwrap().clone();
|
let y = *y.downcast_ref::<char>().unwrap();
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
"==" => return Ok(Some((x == y).into())),
|
"==" => return Ok(Some((x == y).into())),
|
||||||
@ -2102,8 +2134,8 @@ fn run_builtin_binary_op(
|
|||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
{
|
{
|
||||||
if args_type == TypeId::of::<FLOAT>() {
|
if args_type == TypeId::of::<FLOAT>() {
|
||||||
let x = x.downcast_ref::<FLOAT>().unwrap().clone();
|
let x = *x.downcast_ref::<FLOAT>().unwrap();
|
||||||
let y = y.downcast_ref::<FLOAT>().unwrap().clone();
|
let y = *y.downcast_ref::<FLOAT>().unwrap();
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
"+" => return Ok(Some((x + y).into())),
|
"+" => return Ok(Some((x + y).into())),
|
||||||
@ -2142,7 +2174,7 @@ fn run_builtin_op_assignment(
|
|||||||
|
|
||||||
if args_type == TypeId::of::<INT>() {
|
if args_type == TypeId::of::<INT>() {
|
||||||
let x = x.downcast_mut::<INT>().unwrap();
|
let x = x.downcast_mut::<INT>().unwrap();
|
||||||
let y = y.downcast_ref::<INT>().unwrap().clone();
|
let y = *y.downcast_ref::<INT>().unwrap();
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
match op {
|
match op {
|
||||||
@ -2178,7 +2210,7 @@ fn run_builtin_op_assignment(
|
|||||||
}
|
}
|
||||||
} else if args_type == TypeId::of::<bool>() {
|
} else if args_type == TypeId::of::<bool>() {
|
||||||
let x = x.downcast_mut::<bool>().unwrap();
|
let x = x.downcast_mut::<bool>().unwrap();
|
||||||
let y = y.downcast_ref::<bool>().unwrap().clone();
|
let y = *y.downcast_ref::<bool>().unwrap();
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
"&=" => return Ok(Some(*x = *x && y)),
|
"&=" => return Ok(Some(*x = *x && y)),
|
||||||
@ -2199,7 +2231,7 @@ fn run_builtin_op_assignment(
|
|||||||
{
|
{
|
||||||
if args_type == TypeId::of::<FLOAT>() {
|
if args_type == TypeId::of::<FLOAT>() {
|
||||||
let x = x.downcast_mut::<FLOAT>().unwrap();
|
let x = x.downcast_mut::<FLOAT>().unwrap();
|
||||||
let y = y.downcast_ref::<FLOAT>().unwrap().clone();
|
let y = *y.downcast_ref::<FLOAT>().unwrap();
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
"+=" => return Ok(Some(*x += y)),
|
"+=" => return Ok(Some(*x += y)),
|
||||||
|
111
src/module.rs
111
src/module.rs
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use crate::any::{Dynamic, Variant};
|
use crate::any::{Dynamic, Variant};
|
||||||
use crate::calc_fn_hash;
|
use crate::calc_fn_hash;
|
||||||
use crate::engine::{Engine, FunctionsLib};
|
use crate::engine::{make_getter, make_setter, Engine, FunctionsLib, FUNC_INDEXER};
|
||||||
use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn};
|
use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn};
|
||||||
use crate::parser::{
|
use crate::parser::{
|
||||||
FnAccess,
|
FnAccess,
|
||||||
@ -378,9 +378,9 @@ impl Module {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a Rust function taking two parameters into the module, returning a hash key.
|
/// Set a Rust getter function taking one mutable parameter, returning a hash key.
|
||||||
///
|
///
|
||||||
/// If there is a similar existing Rust function, it is replaced.
|
/// If there is a similar existing Rust getter function, it is replaced.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
@ -388,7 +388,30 @@ impl Module {
|
|||||||
/// use rhai::Module;
|
/// use rhai::Module;
|
||||||
///
|
///
|
||||||
/// let mut module = Module::new();
|
/// let mut module = Module::new();
|
||||||
/// let hash = module.set_fn_2("calc", |x: i64, y: String| {
|
/// let hash = module.set_getter_fn("value", |x: &mut i64| { Ok(*x) });
|
||||||
|
/// assert!(module.get_fn(hash).is_some());
|
||||||
|
/// ```
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
pub fn set_getter_fn<A: Variant + Clone, T: Variant + Clone>(
|
||||||
|
&mut self,
|
||||||
|
name: impl Into<String>,
|
||||||
|
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A) -> FuncReturn<T> + 'static,
|
||||||
|
#[cfg(feature = "sync")] func: impl Fn(&mut A) -> FuncReturn<T> + Send + Sync + 'static,
|
||||||
|
) -> u64 {
|
||||||
|
self.set_fn_1_mut(make_getter(&name.into()), func)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a Rust function taking two parameters into the module, returning a hash key.
|
||||||
|
///
|
||||||
|
/// If there is a similar existing Rust function, it is replaced.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::{Module, ImmutableString};
|
||||||
|
///
|
||||||
|
/// let mut module = Module::new();
|
||||||
|
/// let hash = module.set_fn_2("calc", |x: i64, y: ImmutableString| {
|
||||||
/// Ok(x + y.len() as i64)
|
/// Ok(x + y.len() as i64)
|
||||||
/// });
|
/// });
|
||||||
/// assert!(module.get_fn(hash).is_some());
|
/// assert!(module.get_fn(hash).is_some());
|
||||||
@ -417,13 +440,15 @@ impl Module {
|
|||||||
/// Set a Rust function taking two parameters (the first one mutable) into the module,
|
/// Set a Rust function taking two parameters (the first one mutable) into the module,
|
||||||
/// returning a hash key.
|
/// returning a hash key.
|
||||||
///
|
///
|
||||||
|
/// If there is a similar existing Rust function, it is replaced.
|
||||||
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Module;
|
/// use rhai::{Module, ImmutableString};
|
||||||
///
|
///
|
||||||
/// let mut module = Module::new();
|
/// let mut module = Module::new();
|
||||||
/// let hash = module.set_fn_2_mut("calc", |x: &mut i64, y: String| {
|
/// let hash = module.set_fn_2_mut("calc", |x: &mut i64, y: ImmutableString| {
|
||||||
/// *x += y.len() as i64; Ok(*x)
|
/// *x += y.len() as i64; Ok(*x)
|
||||||
/// });
|
/// });
|
||||||
/// assert!(module.get_fn(hash).is_some());
|
/// assert!(module.get_fn(hash).is_some());
|
||||||
@ -449,6 +474,59 @@ impl Module {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set a Rust setter function taking two parameters (the first one mutable) into the module,
|
||||||
|
/// returning a hash key.
|
||||||
|
///
|
||||||
|
/// If there is a similar existing setter Rust function, it is replaced.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::{Module, ImmutableString};
|
||||||
|
///
|
||||||
|
/// let mut module = Module::new();
|
||||||
|
/// let hash = module.set_setter_fn("value", |x: &mut i64, y: ImmutableString| {
|
||||||
|
/// *x = y.len() as i64;
|
||||||
|
/// Ok(())
|
||||||
|
/// });
|
||||||
|
/// assert!(module.get_fn(hash).is_some());
|
||||||
|
/// ```
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
pub fn set_setter_fn<A: Variant + Clone, B: Variant + Clone>(
|
||||||
|
&mut self,
|
||||||
|
name: impl Into<String>,
|
||||||
|
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> FuncReturn<()> + 'static,
|
||||||
|
#[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> FuncReturn<()> + Send + Sync + 'static,
|
||||||
|
) -> u64 {
|
||||||
|
self.set_fn_2_mut(make_setter(&name.into()), func)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a Rust indexer function taking two parameters (the first one mutable) into the module,
|
||||||
|
/// returning a hash key.
|
||||||
|
///
|
||||||
|
/// If there is a similar existing setter Rust function, it is replaced.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::{Module, ImmutableString};
|
||||||
|
///
|
||||||
|
/// let mut module = Module::new();
|
||||||
|
/// let hash = module.set_indexer_fn(|x: &mut i64, y: ImmutableString| {
|
||||||
|
/// Ok(*x + y.len() as i64)
|
||||||
|
/// });
|
||||||
|
/// assert!(module.get_fn(hash).is_some());
|
||||||
|
/// ```
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
pub fn set_indexer_fn<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
|
||||||
|
&mut self,
|
||||||
|
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> FuncReturn<T> + 'static,
|
||||||
|
#[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> FuncReturn<T> + Send + Sync + 'static,
|
||||||
|
) -> u64 {
|
||||||
|
self.set_fn_2_mut(FUNC_INDEXER, func)
|
||||||
|
}
|
||||||
|
|
||||||
/// Set a Rust function taking three parameters into the module, returning a hash key.
|
/// Set a Rust function taking three parameters into the module, returning a hash key.
|
||||||
///
|
///
|
||||||
/// If there is a similar existing Rust function, it is replaced.
|
/// If there is a similar existing Rust function, it is replaced.
|
||||||
@ -456,10 +534,10 @@ impl Module {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Module;
|
/// use rhai::{Module, ImmutableString};
|
||||||
///
|
///
|
||||||
/// let mut module = Module::new();
|
/// let mut module = Module::new();
|
||||||
/// let hash = module.set_fn_3("calc", |x: i64, y: String, z: i64| {
|
/// let hash = module.set_fn_3("calc", |x: i64, y: ImmutableString, z: i64| {
|
||||||
/// Ok(x + y.len() as i64 + z)
|
/// Ok(x + y.len() as i64 + z)
|
||||||
/// });
|
/// });
|
||||||
/// assert!(module.get_fn(hash).is_some());
|
/// assert!(module.get_fn(hash).is_some());
|
||||||
@ -499,10 +577,10 @@ impl Module {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Module;
|
/// use rhai::{Module, ImmutableString};
|
||||||
///
|
///
|
||||||
/// let mut module = Module::new();
|
/// let mut module = Module::new();
|
||||||
/// let hash = module.set_fn_3_mut("calc", |x: &mut i64, y: String, z: i64| {
|
/// let hash = module.set_fn_3_mut("calc", |x: &mut i64, y: ImmutableString, z: i64| {
|
||||||
/// *x += y.len() as i64 + z; Ok(*x)
|
/// *x += y.len() as i64 + z; Ok(*x)
|
||||||
/// });
|
/// });
|
||||||
/// assert!(module.get_fn(hash).is_some());
|
/// assert!(module.get_fn(hash).is_some());
|
||||||
@ -541,10 +619,10 @@ impl Module {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Module;
|
/// use rhai::{Module, ImmutableString};
|
||||||
///
|
///
|
||||||
/// let mut module = Module::new();
|
/// let mut module = Module::new();
|
||||||
/// let hash = module.set_fn_3("calc", |x: i64, y: String, z: i64, _w: ()| {
|
/// let hash = module.set_fn_4("calc", |x: i64, y: ImmutableString, z: i64, _w: ()| {
|
||||||
/// Ok(x + y.len() as i64 + z)
|
/// Ok(x + y.len() as i64 + z)
|
||||||
/// });
|
/// });
|
||||||
/// assert!(module.get_fn(hash).is_some());
|
/// assert!(module.get_fn(hash).is_some());
|
||||||
@ -583,7 +661,7 @@ impl Module {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a Rust function taking three parameters (the first one mutable) into the module,
|
/// Set a Rust function taking four parameters (the first one mutable) into the module,
|
||||||
/// returning a hash key.
|
/// returning a hash key.
|
||||||
///
|
///
|
||||||
/// If there is a similar existing Rust function, it is replaced.
|
/// If there is a similar existing Rust function, it is replaced.
|
||||||
@ -591,10 +669,10 @@ impl Module {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Module;
|
/// use rhai::{Module, ImmutableString};
|
||||||
///
|
///
|
||||||
/// let mut module = Module::new();
|
/// let mut module = Module::new();
|
||||||
/// let hash = module.set_fn_3_mut("calc", |x: &mut i64, y: String, z: i64, _w: ()| {
|
/// let hash = module.set_fn_4_mut("calc", |x: &mut i64, y: ImmutableString, z: i64, _w: ()| {
|
||||||
/// *x += y.len() as i64 + z; Ok(*x)
|
/// *x += y.len() as i64 + z; Ok(*x)
|
||||||
/// });
|
/// });
|
||||||
/// assert!(module.get_fn(hash).is_some());
|
/// assert!(module.get_fn(hash).is_some());
|
||||||
@ -1044,6 +1122,9 @@ mod stat {
|
|||||||
|
|
||||||
/// Module resolution service that serves modules added into it.
|
/// Module resolution service that serves modules added into it.
|
||||||
///
|
///
|
||||||
|
/// `StaticModuleResolver` is a smart pointer to a `HashMap<String, Module>`.
|
||||||
|
/// It can simply be treated as `&HashMap<String, Module>`.
|
||||||
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -122,7 +122,7 @@ fn call_fn_with_constant_arguments(
|
|||||||
state
|
state
|
||||||
.engine
|
.engine
|
||||||
.call_fn_raw(
|
.call_fn_raw(
|
||||||
None,
|
&mut Scope::new(),
|
||||||
&mut Default::default(),
|
&mut Default::default(),
|
||||||
state.lib,
|
state.lib,
|
||||||
fn_name,
|
fn_name,
|
||||||
@ -138,7 +138,7 @@ fn call_fn_with_constant_arguments(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Optimize a statement.
|
/// Optimize a statement.
|
||||||
fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -> Stmt {
|
fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
||||||
match stmt {
|
match stmt {
|
||||||
// if expr { Noop }
|
// if expr { Noop }
|
||||||
Stmt::IfThenElse(x) if matches!(x.1, Stmt::Noop(_)) => {
|
Stmt::IfThenElse(x) if matches!(x.1, Stmt::Noop(_)) => {
|
||||||
@ -359,11 +359,13 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Optimize an expression.
|
/// Optimize an expression.
|
||||||
fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
||||||
// These keywords are handled specially
|
// These keywords are handled specially
|
||||||
const DONT_EVAL_KEYWORDS: [&str; 3] = [KEYWORD_PRINT, KEYWORD_DEBUG, KEYWORD_EVAL];
|
const DONT_EVAL_KEYWORDS: [&str; 3] = [KEYWORD_PRINT, KEYWORD_DEBUG, KEYWORD_EVAL];
|
||||||
|
|
||||||
match expr {
|
match expr {
|
||||||
|
// expr - do not promote because there is a reason it is wrapped in an `Expr::Expr`
|
||||||
|
Expr::Expr(x) => Expr::Expr(Box::new(optimize_expr(*x, state))),
|
||||||
// ( stmt )
|
// ( stmt )
|
||||||
Expr::Stmt(x) => match optimize_stmt(x.0, state, true) {
|
Expr::Stmt(x) => match optimize_stmt(x.0, state, true) {
|
||||||
// ( Noop ) -> ()
|
// ( Noop ) -> ()
|
||||||
|
@ -105,6 +105,10 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
lib.set_fn_1_mut("len", |list: &mut Array| Ok(list.len() as INT));
|
lib.set_fn_1_mut("len", |list: &mut Array| Ok(list.len() as INT));
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
lib.set_getter_fn("len", |list: &mut Array| Ok(list.len() as INT));
|
||||||
|
|
||||||
lib.set_fn_1_mut("clear", |list: &mut Array| {
|
lib.set_fn_1_mut("clear", |list: &mut Array| {
|
||||||
list.clear();
|
list.clear();
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -43,6 +43,18 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
|
|||||||
lib.set_fn_1("is_finite", |x: FLOAT| Ok(x.is_finite()));
|
lib.set_fn_1("is_finite", |x: FLOAT| Ok(x.is_finite()));
|
||||||
lib.set_fn_1("is_infinite", |x: FLOAT| Ok(x.is_infinite()));
|
lib.set_fn_1("is_infinite", |x: FLOAT| Ok(x.is_infinite()));
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
{
|
||||||
|
lib.set_getter_fn("floor", |x: &mut FLOAT| Ok(x.floor()));
|
||||||
|
lib.set_getter_fn("ceiling", |x: &mut FLOAT| Ok(x.ceil()));
|
||||||
|
lib.set_getter_fn("round", |x: &mut FLOAT| Ok(x.ceil()));
|
||||||
|
lib.set_getter_fn("int", |x: &mut FLOAT| Ok(x.trunc()));
|
||||||
|
lib.set_getter_fn("fraction", |x: &mut FLOAT| Ok(x.fract()));
|
||||||
|
lib.set_getter_fn("is_nan", |x: &mut FLOAT| Ok(x.is_nan()));
|
||||||
|
lib.set_getter_fn("is_finite", |x: &mut FLOAT| Ok(x.is_finite()));
|
||||||
|
lib.set_getter_fn("is_infinite", |x: &mut FLOAT| Ok(x.is_infinite()));
|
||||||
|
}
|
||||||
|
|
||||||
// Register conversion functions
|
// Register conversion functions
|
||||||
lib.set_fn_1("to_float", |x: INT| Ok(x as FLOAT));
|
lib.set_fn_1("to_float", |x: INT| Ok(x as FLOAT));
|
||||||
lib.set_fn_1("to_float", |x: f32| Ok(x as FLOAT));
|
lib.set_fn_1("to_float", |x: f32| Ok(x as FLOAT));
|
||||||
|
@ -101,22 +101,26 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
{
|
{
|
||||||
lib.set_fn_2("+", |x: ImmutableString, y: Array| Ok(format!("{}{:?}", x, y)));
|
lib.set_fn_2_mut("+", |x: &mut ImmutableString, y: Array| Ok(format!("{}{:?}", x, y)));
|
||||||
lib.set_fn_2_mut("+", |x: &mut Array, y: ImmutableString| Ok(format!("{:?}{}", x, y)));
|
lib.set_fn_2_mut("+", |x: &mut Array, y: ImmutableString| Ok(format!("{:?}{}", x, y)));
|
||||||
}
|
}
|
||||||
|
|
||||||
lib.set_fn_1("len", |s: ImmutableString| Ok(s.chars().count() as INT));
|
lib.set_fn_1_mut("len", |s: &mut ImmutableString| Ok(s.chars().count() as INT));
|
||||||
lib.set_fn_2(
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
lib.set_getter_fn("len", |s: &mut ImmutableString| Ok(s.chars().count() as INT));
|
||||||
|
|
||||||
|
lib.set_fn_2_mut(
|
||||||
"contains",
|
"contains",
|
||||||
|s: ImmutableString, ch: char| Ok(s.contains(ch)),
|
|s: &mut ImmutableString, ch: char| Ok(s.contains(ch)),
|
||||||
);
|
);
|
||||||
lib.set_fn_2(
|
lib.set_fn_2_mut(
|
||||||
"contains",
|
"contains",
|
||||||
|s: ImmutableString, find: ImmutableString| Ok(s.contains(find.as_str())),
|
|s: &mut ImmutableString, find: ImmutableString| Ok(s.contains(find.as_str())),
|
||||||
);
|
);
|
||||||
lib.set_fn_3(
|
lib.set_fn_3_mut(
|
||||||
"index_of",
|
"index_of",
|
||||||
|s: ImmutableString, ch: char, start: INT| {
|
|s: &mut ImmutableString, ch: char, start: INT| {
|
||||||
let start = if start < 0 {
|
let start = if start < 0 {
|
||||||
0
|
0
|
||||||
} else if (start as usize) >= s.chars().count() {
|
} else if (start as usize) >= s.chars().count() {
|
||||||
@ -131,17 +135,17 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
|
|||||||
.unwrap_or(-1 as INT))
|
.unwrap_or(-1 as INT))
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
lib.set_fn_2(
|
lib.set_fn_2_mut(
|
||||||
"index_of",
|
"index_of",
|
||||||
|s: ImmutableString, ch: char| {
|
|s: &mut ImmutableString, ch: char| {
|
||||||
Ok(s.find(ch)
|
Ok(s.find(ch)
|
||||||
.map(|index| s[0..index].chars().count() as INT)
|
.map(|index| s[0..index].chars().count() as INT)
|
||||||
.unwrap_or(-1 as INT))
|
.unwrap_or(-1 as INT))
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
lib.set_fn_3(
|
lib.set_fn_3_mut(
|
||||||
"index_of",
|
"index_of",
|
||||||
|s: ImmutableString, find: ImmutableString, start: INT| {
|
|s: &mut ImmutableString, find: ImmutableString, start: INT| {
|
||||||
let start = if start < 0 {
|
let start = if start < 0 {
|
||||||
0
|
0
|
||||||
} else if (start as usize) >= s.chars().count() {
|
} else if (start as usize) >= s.chars().count() {
|
||||||
@ -156,9 +160,9 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
|
|||||||
.unwrap_or(-1 as INT))
|
.unwrap_or(-1 as INT))
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
lib.set_fn_2(
|
lib.set_fn_2_mut(
|
||||||
"index_of",
|
"index_of",
|
||||||
|s: ImmutableString, find: ImmutableString| {
|
|s: &mut ImmutableString, find: ImmutableString| {
|
||||||
Ok(s.find(find.as_str())
|
Ok(s.find(find.as_str())
|
||||||
.map(|index| s[0..index].chars().count() as INT)
|
.map(|index| s[0..index].chars().count() as INT)
|
||||||
.unwrap_or(-1 as INT))
|
.unwrap_or(-1 as INT))
|
||||||
|
@ -10,6 +10,9 @@ use crate::token::Position;
|
|||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
use crate::stdlib::time::Instant;
|
use crate::stdlib::time::Instant;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
use crate::parser::FLOAT;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
|
def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
|
||||||
// Register date/time functions
|
// Register date/time functions
|
||||||
@ -70,27 +73,29 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
|
|||||||
lib.set_fn_2("==", eq::<Instant>);
|
lib.set_fn_2("==", eq::<Instant>);
|
||||||
lib.set_fn_2("!=", ne::<Instant>);
|
lib.set_fn_2("!=", ne::<Instant>);
|
||||||
|
|
||||||
lib.set_fn_1(
|
#[cfg(not(feature = "no_float"))]
|
||||||
"elapsed",
|
fn elapsed (timestamp: &mut Instant) -> Result<FLOAT, Box<EvalAltResult>> {
|
||||||
|timestamp: Instant| {
|
Ok(timestamp.elapsed().as_secs_f64())
|
||||||
#[cfg(not(feature = "no_float"))]
|
}
|
||||||
return Ok(timestamp.elapsed().as_secs_f64());
|
|
||||||
|
|
||||||
#[cfg(feature = "no_float")]
|
#[cfg(feature = "no_float")]
|
||||||
{
|
fn elapsed (timestamp: &mut Instant) -> Result<INT, Box<EvalAltResult>> {
|
||||||
let seconds = timestamp.elapsed().as_secs();
|
let seconds = timestamp.elapsed().as_secs();
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
{
|
{
|
||||||
if seconds > (MAX_INT as u64) {
|
if seconds > (MAX_INT as u64) {
|
||||||
return Err(Box::new(EvalAltResult::ErrorArithmetic(
|
return Err(Box::new(EvalAltResult::ErrorArithmetic(
|
||||||
format!("Integer overflow for timestamp.elapsed(): {}", seconds),
|
format!("Integer overflow for timestamp.elapsed: {}", seconds),
|
||||||
Position::none(),
|
Position::none(),
|
||||||
)));
|
)));
|
||||||
}
|
|
||||||
}
|
|
||||||
return Ok(seconds as INT);
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
);
|
Ok(seconds as INT)
|
||||||
|
}
|
||||||
|
|
||||||
|
lib.set_fn_1_mut("elapsed", elapsed);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
lib.set_getter_fn("elapsed", elapsed);
|
||||||
});
|
});
|
||||||
|
@ -23,6 +23,7 @@ use crate::stdlib::{
|
|||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
format,
|
format,
|
||||||
iter::{empty, Peekable},
|
iter::{empty, Peekable},
|
||||||
|
mem,
|
||||||
num::NonZeroUsize,
|
num::NonZeroUsize,
|
||||||
ops::{Add, Deref, DerefMut},
|
ops::{Add, Deref, DerefMut},
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
@ -391,6 +392,8 @@ pub enum Expr {
|
|||||||
Property(Box<((String, String, String), Position)>),
|
Property(Box<((String, String, String), Position)>),
|
||||||
/// { stmt }
|
/// { stmt }
|
||||||
Stmt(Box<(Stmt, Position)>),
|
Stmt(Box<(Stmt, Position)>),
|
||||||
|
/// Wrapped expression - should not be optimized away.
|
||||||
|
Expr(Box<Expr>),
|
||||||
/// func(expr, ... ) - ((function name, native_only, position), optional modules, hash, arguments, optional default value)
|
/// func(expr, ... ) - ((function name, native_only, position), optional modules, hash, arguments, optional default value)
|
||||||
/// Use `Cow<'static, str>` because a lot of operators (e.g. `==`, `>=`) are implemented as function calls
|
/// Use `Cow<'static, str>` because a lot of operators (e.g. `==`, `>=`) are implemented as function calls
|
||||||
/// and the function names are predictable, so no need to allocate a new `String`.
|
/// and the function names are predictable, so no need to allocate a new `String`.
|
||||||
@ -441,6 +444,8 @@ impl Expr {
|
|||||||
/// Panics when the expression is not constant.
|
/// Panics when the expression is not constant.
|
||||||
pub fn get_constant_value(&self) -> Dynamic {
|
pub fn get_constant_value(&self) -> Dynamic {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Expr(x) => x.get_constant_value(),
|
||||||
|
|
||||||
Self::IntegerConstant(x) => x.0.into(),
|
Self::IntegerConstant(x) => x.0.into(),
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Self::FloatConstant(x) => x.0.into(),
|
Self::FloatConstant(x) => x.0.into(),
|
||||||
@ -475,6 +480,8 @@ impl Expr {
|
|||||||
/// Panics when the expression is not constant.
|
/// Panics when the expression is not constant.
|
||||||
pub fn get_constant_str(&self) -> String {
|
pub fn get_constant_str(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Expr(x) => x.get_constant_str(),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Self::FloatConstant(x) => x.0.to_string(),
|
Self::FloatConstant(x) => x.0.to_string(),
|
||||||
|
|
||||||
@ -494,6 +501,8 @@ impl Expr {
|
|||||||
/// Get the `Position` of the expression.
|
/// Get the `Position` of the expression.
|
||||||
pub fn position(&self) -> Position {
|
pub fn position(&self) -> Position {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Expr(x) => x.position(),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Self::FloatConstant(x) => x.1,
|
Self::FloatConstant(x) => x.1,
|
||||||
|
|
||||||
@ -519,6 +528,11 @@ impl Expr {
|
|||||||
/// Override the `Position` of the expression.
|
/// Override the `Position` of the expression.
|
||||||
pub(crate) fn set_position(mut self, new_pos: Position) -> Self {
|
pub(crate) fn set_position(mut self, new_pos: Position) -> Self {
|
||||||
match &mut self {
|
match &mut self {
|
||||||
|
Self::Expr(ref mut x) => {
|
||||||
|
let expr = mem::take(x);
|
||||||
|
*x = Box::new(expr.set_position(new_pos));
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Self::FloatConstant(x) => x.1 = new_pos,
|
Self::FloatConstant(x) => x.1 = new_pos,
|
||||||
|
|
||||||
@ -550,6 +564,8 @@ impl Expr {
|
|||||||
/// A pure expression has no side effects.
|
/// A pure expression has no side effects.
|
||||||
pub fn is_pure(&self) -> bool {
|
pub fn is_pure(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Expr(x) => x.is_pure(),
|
||||||
|
|
||||||
Self::Array(x) => x.0.iter().all(Self::is_pure),
|
Self::Array(x) => x.0.iter().all(Self::is_pure),
|
||||||
|
|
||||||
Self::Index(x) | Self::And(x) | Self::Or(x) | Self::In(x) => {
|
Self::Index(x) | Self::And(x) | Self::Or(x) | Self::In(x) => {
|
||||||
@ -568,6 +584,8 @@ impl Expr {
|
|||||||
/// Is the expression a constant?
|
/// Is the expression a constant?
|
||||||
pub fn is_constant(&self) -> bool {
|
pub fn is_constant(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Expr(x) => x.is_constant(),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Self::FloatConstant(_) => true,
|
Self::FloatConstant(_) => true,
|
||||||
|
|
||||||
@ -598,6 +616,8 @@ impl Expr {
|
|||||||
/// Is a particular token allowed as a postfix operator to this expression?
|
/// Is a particular token allowed as a postfix operator to this expression?
|
||||||
pub fn is_valid_postfix(&self, token: &Token) -> bool {
|
pub fn is_valid_postfix(&self, token: &Token) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Expr(x) => x.is_valid_postfix(token),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Self::FloatConstant(_) => false,
|
Self::FloatConstant(_) => false,
|
||||||
|
|
||||||
@ -996,7 +1016,7 @@ fn parse_index_chain<'a>(
|
|||||||
(Token::LeftBracket, _) => {
|
(Token::LeftBracket, _) => {
|
||||||
let idx_pos = eat_token(input, Token::LeftBracket);
|
let idx_pos = eat_token(input, Token::LeftBracket);
|
||||||
// Recursively parse the indexing chain, right-binding each
|
// Recursively parse the indexing chain, right-binding each
|
||||||
let idx = parse_index_chain(
|
let idx_expr = parse_index_chain(
|
||||||
input,
|
input,
|
||||||
state,
|
state,
|
||||||
idx_expr,
|
idx_expr,
|
||||||
@ -1005,10 +1025,20 @@ fn parse_index_chain<'a>(
|
|||||||
allow_stmt_expr,
|
allow_stmt_expr,
|
||||||
)?;
|
)?;
|
||||||
// Indexing binds to right
|
// Indexing binds to right
|
||||||
Ok(Expr::Index(Box::new((lhs, idx, pos))))
|
Ok(Expr::Index(Box::new((lhs, idx_expr, pos))))
|
||||||
}
|
}
|
||||||
// Otherwise terminate the indexing chain
|
// Otherwise terminate the indexing chain
|
||||||
_ => Ok(Expr::Index(Box::new((lhs, idx_expr, pos)))),
|
_ => {
|
||||||
|
match idx_expr {
|
||||||
|
// Terminate with an `Expr::Expr` wrapper to prevent the last index expression
|
||||||
|
// inside brackets to be mis-parsed as another level of indexing, or a
|
||||||
|
// dot expression/function call to be mis-parsed as following the indexing chain.
|
||||||
|
Expr::Index(_) | Expr::Dot(_) | Expr::FnCall(_) => Ok(Expr::Index(
|
||||||
|
Box::new((lhs, Expr::Expr(Box::new(idx_expr)), pos)),
|
||||||
|
)),
|
||||||
|
_ => Ok(Expr::Index(Box::new((lhs, idx_expr, pos)))),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(*pos)),
|
(Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(*pos)),
|
||||||
@ -1732,37 +1762,34 @@ fn parse_binary_op<'a>(
|
|||||||
let cmp_def = Some(false.into());
|
let cmp_def = Some(false.into());
|
||||||
let op = op_token.syntax();
|
let op = op_token.syntax();
|
||||||
let hash = calc_fn_hash(empty(), &op, 2, empty());
|
let hash = calc_fn_hash(empty(), &op, 2, empty());
|
||||||
|
let op = (op, true, pos);
|
||||||
|
|
||||||
let mut args = StaticVec::new();
|
let mut args = StaticVec::new();
|
||||||
args.push(root);
|
args.push(root);
|
||||||
args.push(rhs);
|
args.push(rhs);
|
||||||
|
|
||||||
root = match op_token {
|
root = match op_token {
|
||||||
Token::Plus => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
|
Token::Plus
|
||||||
Token::Minus => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
|
| Token::Minus
|
||||||
Token::Multiply => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
|
| Token::Multiply
|
||||||
Token::Divide => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
|
| Token::Divide
|
||||||
|
| Token::LeftShift
|
||||||
|
| Token::RightShift
|
||||||
|
| Token::Modulo
|
||||||
|
| Token::PowerOf
|
||||||
|
| Token::Ampersand
|
||||||
|
| Token::Pipe
|
||||||
|
| Token::XOr => Expr::FnCall(Box::new((op, None, hash, args, None))),
|
||||||
|
|
||||||
Token::LeftShift => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
|
// '!=' defaults to true when passed invalid operands
|
||||||
Token::RightShift => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
|
Token::NotEqualsTo => Expr::FnCall(Box::new((op, None, hash, args, Some(true.into())))),
|
||||||
Token::Modulo => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
|
|
||||||
Token::PowerOf => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
|
|
||||||
|
|
||||||
// Comparison operators default to false when passed invalid operands
|
// Comparison operators default to false when passed invalid operands
|
||||||
Token::EqualsTo => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, cmp_def))),
|
Token::EqualsTo
|
||||||
Token::NotEqualsTo => {
|
| Token::LessThan
|
||||||
Expr::FnCall(Box::new(((op, true, pos), None, hash, args, cmp_def)))
|
| Token::LessThanEqualsTo
|
||||||
}
|
| Token::GreaterThan
|
||||||
Token::LessThan => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, cmp_def))),
|
| Token::GreaterThanEqualsTo => Expr::FnCall(Box::new((op, None, hash, args, cmp_def))),
|
||||||
Token::LessThanEqualsTo => {
|
|
||||||
Expr::FnCall(Box::new(((op, true, pos), None, hash, args, cmp_def)))
|
|
||||||
}
|
|
||||||
Token::GreaterThan => {
|
|
||||||
Expr::FnCall(Box::new(((op, true, pos), None, hash, args, cmp_def)))
|
|
||||||
}
|
|
||||||
Token::GreaterThanEqualsTo => {
|
|
||||||
Expr::FnCall(Box::new(((op, true, pos), None, hash, args, cmp_def)))
|
|
||||||
}
|
|
||||||
|
|
||||||
Token::Or => {
|
Token::Or => {
|
||||||
let rhs = args.pop();
|
let rhs = args.pop();
|
||||||
@ -1774,10 +1801,6 @@ fn parse_binary_op<'a>(
|
|||||||
let current_lhs = args.pop();
|
let current_lhs = args.pop();
|
||||||
Expr::And(Box::new((current_lhs, rhs, pos)))
|
Expr::And(Box::new((current_lhs, rhs, pos)))
|
||||||
}
|
}
|
||||||
Token::Ampersand => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
|
|
||||||
Token::Pipe => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
|
|
||||||
Token::XOr => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
|
|
||||||
|
|
||||||
Token::In => {
|
Token::In => {
|
||||||
let rhs = args.pop();
|
let rhs = args.pop();
|
||||||
let current_lhs = args.pop();
|
let current_lhs = args.pop();
|
||||||
|
@ -26,7 +26,7 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
|
|||||||
let y = [4, 5];
|
let y = [4, 5];
|
||||||
x.append(y);
|
x.append(y);
|
||||||
|
|
||||||
x.len() + r
|
x.len + r
|
||||||
"
|
"
|
||||||
)?,
|
)?,
|
||||||
14
|
14
|
||||||
|
@ -48,7 +48,7 @@ fn test_for_object() -> Result<(), Box<EvalAltResult>> {
|
|||||||
sum += value;
|
sum += value;
|
||||||
}
|
}
|
||||||
|
|
||||||
keys.len() + sum
|
keys.len + sum
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
assert_eq!(engine.eval::<INT>(script)?, 9);
|
assert_eq!(engine.eval::<INT>(script)?, 9);
|
||||||
|
@ -7,12 +7,23 @@ fn test_functions() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
assert_eq!(engine.eval::<INT>("fn add(x, n) { x + n } add(40, 2)")?, 42);
|
assert_eq!(engine.eval::<INT>("fn add(x, n) { x + n } add(40, 2)")?, 42);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>("fn add(x, n) { x + n } let a = 40; add(a, 2); a")?,
|
||||||
|
40
|
||||||
|
);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>("fn add(x, n) { x + n } let x = 40; x.add(2)")?,
|
engine.eval::<INT>("fn add(x, n) { x + n } let x = 40; x.add(2)")?,
|
||||||
42
|
42
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>("fn add(x, n) { x += n; } let x = 40; x.add(2); x")?,
|
||||||
|
40
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(engine.eval::<INT>("fn mul2(x) { x * 2 } mul2(21)")?, 42);
|
assert_eq!(engine.eval::<INT>("fn mul2(x) { x * 2 } mul2(21)")?, 42);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
@ -21,5 +32,11 @@ fn test_functions() -> Result<(), Box<EvalAltResult>> {
|
|||||||
42
|
42
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>("fn mul2(x) { x *= 2; } let x = 21; x.mul2(); x")?,
|
||||||
|
21
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ fn test_method_call() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<TestStruct>("let x = new_ts(); update(x, 1000); x")?,
|
engine.eval::<TestStruct>("let x = new_ts(); update(x, 1000); x")?,
|
||||||
TestStruct { x: 1 }
|
TestStruct { x: 1001 }
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -20,21 +20,20 @@ fn test_string() -> Result<(), Box<EvalAltResult>> {
|
|||||||
assert!(engine.eval::<bool>(r#"let y = "hello, world!"; 'w' in y"#)?);
|
assert!(engine.eval::<bool>(r#"let y = "hello, world!"; 'w' in y"#)?);
|
||||||
assert!(!engine.eval::<bool>(r#"let y = "hello, world!"; "hey" in y"#)?);
|
assert!(!engine.eval::<bool>(r#"let y = "hello, world!"; "hey" in y"#)?);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_stdlib"))]
|
|
||||||
assert_eq!(engine.eval::<String>(r#""foo" + 123"#)?, "foo123");
|
assert_eq!(engine.eval::<String>(r#""foo" + 123"#)?, "foo123");
|
||||||
|
|
||||||
#[cfg(not(feature = "no_stdlib"))]
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
assert_eq!(engine.eval::<String>("(42).to_string()")?, "42");
|
assert_eq!(engine.eval::<String>("(42).to_string()")?, "42");
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
assert_eq!(engine.eval::<char>(r#"let y = "hello"; y[y.len-1]"#)?, 'o');
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
#[cfg(not(feature = "no_stdlib"))]
|
|
||||||
assert_eq!(engine.eval::<String>(r#""foo" + 123.4556"#)?, "foo123.4556");
|
assert_eq!(engine.eval::<String>(r#""foo" + 123.4556"#)?, "foo123.4556");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_stdlib"))]
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_string_substring() -> Result<(), Box<EvalAltResult>> {
|
fn test_string_substring() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
#![cfg(not(feature = "no_stdlib"))]
|
|
||||||
#![cfg(not(feature = "no_std"))]
|
#![cfg(not(feature = "no_std"))]
|
||||||
|
|
||||||
use rhai::{Engine, EvalAltResult, INT};
|
use rhai::{Engine, EvalAltResult, INT};
|
||||||
|
Loading…
Reference in New Issue
Block a user