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 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 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.
|
||||
@ -300,7 +300,7 @@ let ast = engine.compile(true,
|
||||
r"
|
||||
// a function with two parameters: String and i64
|
||||
fn hello(x, y) {
|
||||
x.len() + y
|
||||
x.len + y
|
||||
}
|
||||
|
||||
// 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 script = "fn calc(x, y) { x + y.len() < 42 }";
|
||||
let script = "fn calc(x, y) { x + y.len < 42 }";
|
||||
|
||||
// Func takes two type 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.
|
||||
|
||||
```rust
|
||||
// Below is a syntax error under 'no_object' because 'len' cannot be called in method style.
|
||||
let result = engine.eval::<i64>("let x = [1, 2, 3]; x.len()")?;
|
||||
// Below is a syntax error under 'no_object' because 'clear' cannot be called in method style.
|
||||
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.
|
||||
@ -1082,7 +1082,7 @@ fn main() -> Result<(), Box<EvalAltResult>>
|
||||
|
||||
// First invocation
|
||||
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;
|
||||
")?;
|
||||
|
||||
@ -1122,6 +1122,7 @@ Comments
|
||||
--------
|
||||
|
||||
Comments are C-style, including '`/*` ... `*/`' pairs and '`//`' for comments to the end of the line.
|
||||
Comments can be nested.
|
||||
|
||||
```rust
|
||||
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 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
|
||||
(enclosed by '`{`' .. '`}`' pairs) is always the return value of the statement. If a statement has no return value
|
||||
(e.g. variable definitions, assignments) then the value will be [`()`].
|
||||
A statement can be used anywhere where an expression is expected. These are called, for lack of a more
|
||||
creative name, "statement expressions." The _last_ statement of a statement block is _always_ the block's
|
||||
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
|
||||
let a = 42; // normal assignment statement
|
||||
@ -1153,16 +1175,17 @@ let a = foo(42); // normal function call 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
|
||||
// ^ notice that 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
|
||||
// ^ the last statement does not require a terminating semicolon (although it also works with 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
|
||||
// it is the last statement of the whole block
|
||||
4 * 10 + 2 // a statement which is just one expression; no ending semicolon is OK
|
||||
// because it is the last statement of the whole block
|
||||
```
|
||||
|
||||
Variables
|
||||
---------
|
||||
|
||||
[variable]: #variables
|
||||
[variables]: #variables
|
||||
|
||||
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:
|
||||
|
||||
| Category | Functions |
|
||||
| ---------------- | ------------------------------------------------------------ |
|
||||
| Trigonometry | `sin`, `cos`, `tan`, `sinh`, `cosh`, `tanh` in degrees |
|
||||
| Arc-trigonometry | `asin`, `acos`, `atan`, `asinh`, `acosh`, `atanh` in degrees |
|
||||
| Square root | `sqrt` |
|
||||
| Exponential | `exp` (base _e_) |
|
||||
| Logarithmic | `ln` (base _e_), `log10` (base 10), `log` (any base) |
|
||||
| Rounding | `floor`, `ceiling`, `round`, `int`, `fraction` |
|
||||
| Conversion | [`to_int`] |
|
||||
| Testing | `is_nan`, `is_finite`, `is_infinite` |
|
||||
| Category | Functions |
|
||||
| ---------------- | --------------------------------------------------------------------- |
|
||||
| Trigonometry | `sin`, `cos`, `tan`, `sinh`, `cosh`, `tanh` in degrees |
|
||||
| Arc-trigonometry | `asin`, `acos`, `atan`, `asinh`, `acosh`, `atanh` in degrees |
|
||||
| Square root | `sqrt` |
|
||||
| Exponential | `exp` (base _e_) |
|
||||
| Logarithmic | `ln` (base _e_), `log10` (base 10), `log` (any base) |
|
||||
| Rounding | `floor`, `ceiling`, `round`, `int`, `fraction` methods and properties |
|
||||
| Conversion | [`to_int`] |
|
||||
| Testing | `is_nan`, `is_finite`, `is_infinite` methods and properties |
|
||||
|
||||
Strings and Chars
|
||||
-----------------
|
||||
@ -1307,8 +1330,8 @@ Strings and Chars
|
||||
[strings]: #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
|
||||
hex ('`\x`_xx_') escape sequences.
|
||||
String and character literals follow C-style formatting, with support for Unicode ('`\u`_xxxx_' or '`\U`_xxxxxxxx_')
|
||||
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,
|
||||
32-bit extended Unicode code points.
|
||||
@ -1388,34 +1411,34 @@ record == "Bob X. Davis: age 42 ❤\n";
|
||||
|
||||
### 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 |
|
||||
| ----------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
|
||||
| `len` | _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 |
|
||||
| `+=` operator, `append` | character/string to append | Adds a character or a string to the end of another string |
|
||||
| `clear` | _none_ | empties the string |
|
||||
| `truncate` | target length | cuts off the string at exactly a specified number of characters |
|
||||
| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string |
|
||||
| `index_of` | 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) |
|
||||
| `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 |
|
||||
| `trim` | _none_ | trims the string of whitespace at the beginning and end |
|
||||
| Function | Parameter(s) | Description |
|
||||
| ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
|
||||
| `len` method and property | _none_ | returns the number of characters (not number of bytes) in the string |
|
||||
| `pad` | 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 |
|
||||
| `clear` | _none_ | empties the string |
|
||||
| `truncate` | target length | cuts off the string at exactly a specified number of characters |
|
||||
| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string |
|
||||
| `index_of` | 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) |
|
||||
| `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 |
|
||||
| `trim` | _none_ | trims the string of whitespace at the beginning and end |
|
||||
|
||||
### Examples
|
||||
|
||||
```rust
|
||||
let full_name == " Bob C. Davis ";
|
||||
full_name.len() == 14;
|
||||
full_name.len == 14;
|
||||
|
||||
full_name.trim();
|
||||
full_name.len() == 12;
|
||||
full_name.len == 12;
|
||||
full_name == "Bob C. Davis";
|
||||
|
||||
full_name.pad(15, '$');
|
||||
full_name.len() == 15;
|
||||
full_name.len == 15;
|
||||
full_name == "Bob C. Davis$$$";
|
||||
|
||||
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.truncate(6);
|
||||
full_name.len() == 6;
|
||||
full_name.len == 6;
|
||||
full_name == "Bob C.";
|
||||
|
||||
full_name.replace("Bob", "John");
|
||||
full_name.len() == 7;
|
||||
full_name.len == 7;
|
||||
full_name == "John C.";
|
||||
|
||||
full_name.contains('C') == true;
|
||||
@ -1443,7 +1466,7 @@ full_name.crop(0, 1);
|
||||
full_name == "C";
|
||||
|
||||
full_name.clear();
|
||||
full_name.len() == 0;
|
||||
full_name.len == 0;
|
||||
```
|
||||
|
||||
Arrays
|
||||
@ -1463,21 +1486,21 @@ Arrays are disabled via the [`no_index`] feature.
|
||||
|
||||
### 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 |
|
||||
| ----------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| `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 | 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 |
|
||||
| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) |
|
||||
| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) |
|
||||
| `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid |
|
||||
| `len` | _none_ | returns the number of elements |
|
||||
| `pad` | element to pad, target length | pads the array with an element to at least a specified length |
|
||||
| `clear` | _none_ | empties the array |
|
||||
| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |
|
||||
| Function | Parameter(s) | Description |
|
||||
| ------------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| `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 | 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 |
|
||||
| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) |
|
||||
| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) |
|
||||
| `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid |
|
||||
| `len` method and property | _none_ | returns the number of elements |
|
||||
| `pad` | element to pad, target length | pads the array with an element to at least a specified length |
|
||||
| `clear` | _none_ | empties the array |
|
||||
| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |
|
||||
|
||||
### 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(999, 4); // insert element at the end
|
||||
|
||||
y.len() == 4;
|
||||
y.len == 4;
|
||||
|
||||
y[0] == 1;
|
||||
y[1] == 2;
|
||||
@ -1504,7 +1527,7 @@ y[1] = 42; // array elements can be reassigned
|
||||
|
||||
y.remove(2) == 3; // remove element
|
||||
|
||||
y.len() == 3;
|
||||
y.len == 3;
|
||||
|
||||
y[2] == 4; // elements after the removed element are shifted
|
||||
|
||||
@ -1528,7 +1551,7 @@ foo == 1;
|
||||
y.push(4); // 4 elements
|
||||
y.push(5); // 5 elements
|
||||
|
||||
y.len() == 5;
|
||||
y.len == 5;
|
||||
|
||||
let first = y.shift(); // remove the first element, 4 elements remaining
|
||||
first == 1;
|
||||
@ -1536,7 +1559,7 @@ first == 1;
|
||||
let last = y.pop(); // remove the last element, 3 elements remaining
|
||||
last == 5;
|
||||
|
||||
y.len() == 3;
|
||||
y.len == 3;
|
||||
|
||||
for item in y { // arrays can be iterated with a 'for' statement
|
||||
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.len() == 10;
|
||||
y.len == 10;
|
||||
|
||||
y.truncate(5); // truncate the array to 5 elements
|
||||
|
||||
y.len() == 5;
|
||||
y.len == 5;
|
||||
|
||||
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:
|
||||
@ -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:
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| ------------ | ---------------------------------- | -------------------------------------------------------- |
|
||||
| `elapsed` | _none_ | returns the number of seconds since the timestamp |
|
||||
| `-` operator | later timestamp, earlier timestamp | returns the number of seconds between the two timestamps |
|
||||
| Function | Parameter(s) | Description |
|
||||
| ------------------ | ---------------------------------- | -------------------------------------------------------- |
|
||||
| `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 |
|
||||
|
||||
### Examples
|
||||
|
||||
@ -1734,7 +1757,7 @@ let now = timestamp();
|
||||
|
||||
// Do some lengthy operation...
|
||||
|
||||
if now.elapsed() > 30.0 {
|
||||
if now.elapsed > 30.0 {
|
||||
print("takes too long (over 30 seconds)!")
|
||||
}
|
||||
```
|
||||
@ -1963,6 +1986,9 @@ println!(result); // prints "Runtime error: 42 is too large! (line 5,
|
||||
Functions
|
||||
---------
|
||||
|
||||
[function]: #functions
|
||||
[functions]: #functions
|
||||
|
||||
Rhai supports defining functions in script (unless disabled with [`no_function`]):
|
||||
|
||||
```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).
|
||||
It is important to remember that all arguments are passed by _value_, so all functions are _pure_
|
||||
(i.e. they never modifytheir arguments).
|
||||
Any update to an argument will **not** be reflected back to the caller. This can introduce subtle bugs, if not careful.
|
||||
(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, especially when using the _method-call_ style.
|
||||
|
||||
```rust
|
||||
fn change(s) { // 's' is passed by value
|
||||
@ -2016,7 +2043,7 @@ fn change(s) { // 's' is passed by value
|
||||
}
|
||||
|
||||
let x = 500;
|
||||
x.change(); // de-sugars to change(x)
|
||||
x.change(); // de-sugars to 'change(x)'
|
||||
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.
|
||||
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
|
||||
let a = new_ts(); // constructor function
|
||||
a.field = 500; // property access
|
||||
a.update(); // method call, 'a' can be changed
|
||||
a.field = 500; // property setter
|
||||
a.update(); // method call, 'a' can be modified
|
||||
|
||||
update(a); // this works, but 'a' is unchanged because only
|
||||
// a COPY of 'a' is passed to 'update' by VALUE
|
||||
update(a); // <- this de-sugars to 'a.update()' this if 'a' is a simple variable
|
||||
// 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.
|
||||
@ -2244,7 +2281,7 @@ let ast = engine.compile(r#"
|
||||
x + 1
|
||||
}
|
||||
fn add_len(x, y) {
|
||||
x + y.len()
|
||||
x + y.len
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
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
|
||||
----------------
|
||||
|
||||
@ -22,13 +28,19 @@ Breaking changes
|
||||
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
|
||||
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
|
||||
------------
|
||||
|
||||
* Set limit on maximum level of nesting expressions and statements to avoid panics during parsing.
|
||||
* 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
|
||||
------------------
|
||||
@ -43,6 +55,11 @@ Speed enhancements
|
||||
(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.
|
||||
* 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
|
||||
|
@ -48,7 +48,7 @@ fn bench_eval_expression_optimized_simple(bench: &mut Bencher) {
|
||||
2 > 1 &&
|
||||
"something" != "nothing" ||
|
||||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
||||
[array, with, spaces].len() <= #{prop:name}.len() &&
|
||||
[array, with, spaces].len <= #{prop:name}.len &&
|
||||
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
||||
"#;
|
||||
|
||||
@ -65,7 +65,7 @@ fn bench_eval_expression_optimized_full(bench: &mut Bencher) {
|
||||
2 > 1 &&
|
||||
"something" != "nothing" ||
|
||||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
||||
[array, with, spaces].len() <= #{prop:name}.len() &&
|
||||
[array, with, spaces].len <= #{prop:name}.len &&
|
||||
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
||||
"#;
|
||||
|
||||
@ -82,7 +82,7 @@ fn bench_eval_call_expression(bench: &mut Bencher) {
|
||||
2 > 1 &&
|
||||
"something" != "nothing" ||
|
||||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
||||
[array, with, spaces].len() <= #{prop:name}.len() &&
|
||||
[array, with, spaces].len <= #{prop:name}.len &&
|
||||
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
||||
"#;
|
||||
|
||||
@ -97,7 +97,7 @@ fn bench_eval_call(bench: &mut Bencher) {
|
||||
2 > 1 &&
|
||||
"something" != "nothing" ||
|
||||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
||||
[array, with, spaces].len() <= #{prop:name}.len() &&
|
||||
[array, with, spaces].len <= #{prop:name}.len &&
|
||||
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
||||
"#;
|
||||
|
||||
|
@ -32,7 +32,7 @@ fn bench_parse_full(bench: &mut Bencher) {
|
||||
2 > 1 &&
|
||||
"something" != "nothing" ||
|
||||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
||||
[array, with, spaces].len() <= #{prop:name}.len() &&
|
||||
[array, with, spaces].len <= #{prop:name}.len &&
|
||||
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
||||
"#;
|
||||
|
||||
@ -94,7 +94,7 @@ fn bench_parse_primes(bench: &mut Bencher) {
|
||||
}
|
||||
|
||||
print("Total " + total_primes_found + " primes.");
|
||||
print("Run time = " + now.elapsed() + " seconds.");
|
||||
print("Run time = " + now.elapsed + " seconds.");
|
||||
"#;
|
||||
|
||||
let mut engine = Engine::new();
|
||||
@ -109,7 +109,7 @@ fn bench_parse_optimize_simple(bench: &mut Bencher) {
|
||||
2 > 1 &&
|
||||
"something" != "nothing" ||
|
||||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
||||
[array, with, spaces].len() <= #{prop:name}.len() &&
|
||||
[array, with, spaces].len <= #{prop:name}.len &&
|
||||
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
||||
"#;
|
||||
|
||||
@ -125,7 +125,7 @@ fn bench_parse_optimize_full(bench: &mut Bencher) {
|
||||
2 > 1 &&
|
||||
"something" != "nothing" ||
|
||||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
||||
[array, with, spaces].len() <= #{prop:name}.len() &&
|
||||
[array, with, spaces].len <= #{prop:name}.len &&
|
||||
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
||||
"#;
|
||||
|
||||
|
@ -17,7 +17,7 @@ let now = timestamp();
|
||||
|
||||
let result = fib(target);
|
||||
|
||||
print("Finished. Run time = " + now.elapsed() + " seconds.");
|
||||
print("Finished. Run time = " + now.elapsed + " seconds.");
|
||||
|
||||
print("Fibonacci number #" + target + " = " + result);
|
||||
|
||||
|
@ -24,9 +24,9 @@ fn mat_gen(n) {
|
||||
}
|
||||
|
||||
fn mat_mul(a, b) {
|
||||
let m = a.len();
|
||||
let n = a[0].len();
|
||||
let p = b[0].len();
|
||||
let m = a.len;
|
||||
let n = a[0].len;
|
||||
let p = b[0].len;
|
||||
|
||||
let b2 = new_mat(n, p);
|
||||
|
||||
@ -38,13 +38,13 @@ fn mat_mul(a, b) {
|
||||
|
||||
let c = new_mat(m, p);
|
||||
|
||||
for i in range(0, c.len()) {
|
||||
for i in range(0, c.len) {
|
||||
let ci = c[i];
|
||||
for j in range(0, ci.len()) {
|
||||
for j in range(0, ci.len) {
|
||||
let b2j = b2[j];
|
||||
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 y = b2j[z];
|
||||
ci[j] += x * y;
|
||||
@ -66,4 +66,4 @@ for i in range(0, SIZE) {
|
||||
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("Run time = " + now.elapsed() + " seconds.");
|
||||
print("Run time = " + now.elapsed + " seconds.");
|
||||
|
||||
if total_primes_found != 9_592 {
|
||||
print("The answer is WRONG! Should be 9,592!");
|
||||
|
@ -10,4 +10,4 @@ while x > 0 {
|
||||
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
|
||||
|
||||
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
|
||||
print(s); // should print 'hello, world?'
|
||||
s[s.len-1] = '?'; // change the string
|
||||
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"))]
|
||||
impl<T: Variant + Clone> From<HashMap<String, T>> for Dynamic {
|
||||
fn from(value: HashMap<String, T>) -> Self {
|
||||
|
@ -1051,8 +1051,7 @@ impl Engine {
|
||||
let mut state = State::new();
|
||||
let args = args.as_mut();
|
||||
|
||||
let result =
|
||||
self.call_script_fn(Some(scope), &mut state, &lib, name, fn_def, args, pos, 0)?;
|
||||
let result = self.call_script_fn(scope, &mut state, &lib, name, fn_def, args, pos, 0)?;
|
||||
|
||||
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
|
||||
fn search_scope<'a>(
|
||||
scope: &'a mut Scope,
|
||||
fn search_scope<'s, 'a>(
|
||||
scope: &'s mut Scope,
|
||||
state: &mut State,
|
||||
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 {
|
||||
Expr::Variable(x) => x.as_ref(),
|
||||
_ => 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 `()`!
|
||||
pub(crate) fn call_fn_raw(
|
||||
&self,
|
||||
scope: Option<&mut Scope>,
|
||||
scope: &mut Scope,
|
||||
state: &mut State,
|
||||
lib: &FunctionsLib,
|
||||
fn_name: &str,
|
||||
@ -615,11 +615,55 @@ impl Engine {
|
||||
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)
|
||||
if !native_only {
|
||||
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 =
|
||||
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));
|
||||
}
|
||||
}
|
||||
@ -631,29 +675,18 @@ impl Engine {
|
||||
.or_else(|| self.packages.get_fn(hashes.0))
|
||||
{
|
||||
// Calling pure function in method-call?
|
||||
let mut this_copy: Option<Dynamic>;
|
||||
let mut this_pointer: Option<&mut Dynamic> = None;
|
||||
|
||||
if func.is_pure() && is_ref && !args.is_empty() {
|
||||
// Clone the original value. It'll be consumed because the function
|
||||
// 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()),
|
||||
));
|
||||
}
|
||||
normalize_first_arg(
|
||||
func.is_pure() && is_ref,
|
||||
&mut this_copy,
|
||||
&mut old_this_ptr,
|
||||
args,
|
||||
);
|
||||
|
||||
// Run external function
|
||||
let result = func.get_native_fn()(args);
|
||||
|
||||
// Restore the original reference
|
||||
if let Some(this_pointer) = this_pointer {
|
||||
mem::replace(args.get_mut(0).unwrap(), this_pointer);
|
||||
}
|
||||
restore_first_arg(old_this_ptr, args);
|
||||
|
||||
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 `()`!
|
||||
pub(crate) fn call_script_fn(
|
||||
&self,
|
||||
scope: Option<&mut Scope>,
|
||||
scope: &mut Scope,
|
||||
state: &mut State,
|
||||
lib: &FunctionsLib,
|
||||
fn_name: &str,
|
||||
@ -753,88 +786,42 @@ impl Engine {
|
||||
let orig_scope_level = state.scope_level;
|
||||
state.scope_level += 1;
|
||||
|
||||
match scope {
|
||||
// Extern scope passed in which is not empty
|
||||
Some(scope) if !scope.is_empty() => {
|
||||
let scope_len = scope.len();
|
||||
let scope_len = scope.len();
|
||||
|
||||
// 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)| {
|
||||
let var_name = unsafe_cast_var_name_to_lifetime(name.as_str(), state);
|
||||
(var_name, ScopeEntryType::Normal, value)
|
||||
}),
|
||||
);
|
||||
// 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)| {
|
||||
let var_name = unsafe_cast_var_name_to_lifetime(name.as_str(), state);
|
||||
(var_name, ScopeEntryType::Normal, value)
|
||||
}),
|
||||
);
|
||||
|
||||
// Evaluate the function at one higher level of call depth
|
||||
let result = self
|
||||
.eval_stmt(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,
|
||||
))),
|
||||
});
|
||||
// Evaluate the function at one higher level of call depth
|
||||
let result = self
|
||||
.eval_stmt(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,
|
||||
))),
|
||||
});
|
||||
|
||||
// Remove all local variables
|
||||
scope.rewind(scope_len);
|
||||
state.scope_level = orig_scope_level;
|
||||
// Remove all local variables
|
||||
scope.rewind(scope_len);
|
||||
state.scope_level = orig_scope_level;
|
||||
|
||||
return 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;
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
// Has a system function an override?
|
||||
@ -892,9 +879,12 @@ impl Engine {
|
||||
}
|
||||
|
||||
// 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::Property(_) => idx_values.push(()), // Store a placeholder - no need to copy the property name
|
||||
Expr::Index(x) | Expr::Dot(x) => {
|
||||
let (lhs, rhs, _) = x.as_ref();
|
||||
|
||||
// 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
|
||||
_ => self.eval_expr(scope, state, lib, &x.0, level)?,
|
||||
_ => self.eval_expr(scope, state, lib, lhs, level)?,
|
||||
};
|
||||
|
||||
// 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);
|
||||
}
|
||||
@ -1347,6 +1339,7 @@ impl Engine {
|
||||
Dynamic(Union::Array(mut rhs_value)) => {
|
||||
let op = "==";
|
||||
let def_value = false.into();
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Call the `==` operator to compare each value
|
||||
for value in rhs_value.iter_mut() {
|
||||
@ -1361,7 +1354,7 @@ impl Engine {
|
||||
);
|
||||
|
||||
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) {
|
||||
return Ok(true.into());
|
||||
@ -1399,6 +1392,8 @@ impl Engine {
|
||||
self.inc_operations(state, expr.position())?;
|
||||
|
||||
match expr {
|
||||
Expr::Expr(x) => self.eval_expr(scope, state, lib, x.as_ref(), level),
|
||||
|
||||
Expr::IntegerConstant(x) => Ok(x.0.into()),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
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 def_val = def_val.as_ref();
|
||||
|
||||
let mut arg_values = args_expr
|
||||
.iter()
|
||||
.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>() {
|
||||
// Handle eval
|
||||
if name == KEYWORD_EVAL && args_expr.len() == 1 {
|
||||
let hash_fn =
|
||||
calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
|
||||
|
||||
@ -1567,7 +1556,8 @@ impl Engine {
|
||||
let pos = args_expr.get(0).position();
|
||||
|
||||
// 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 {
|
||||
// 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)
|
||||
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();
|
||||
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)
|
||||
}
|
||||
@ -1639,9 +1672,8 @@ impl Engine {
|
||||
Ok(x) if x.is_script() => {
|
||||
let args = args.as_mut();
|
||||
let fn_def = x.get_fn_def();
|
||||
let result =
|
||||
self.call_script_fn(None, state, lib, name, fn_def, args, *pos, level)?;
|
||||
Ok(result)
|
||||
let mut scope = Scope::new();
|
||||
self.call_script_fn(&mut scope, state, lib, name, fn_def, args, *pos, level)
|
||||
}
|
||||
Ok(x) => x.get_native_fn()(args.as_mut()).map_err(|err| err.new_position(*pos)),
|
||||
Err(err)
|
||||
@ -1701,9 +1733,9 @@ impl Engine {
|
||||
}
|
||||
|
||||
/// Evaluate a statement
|
||||
pub(crate) fn eval_stmt<'s>(
|
||||
pub(crate) fn eval_stmt(
|
||||
&self,
|
||||
scope: &mut Scope<'s>,
|
||||
scope: &mut Scope,
|
||||
state: &mut State,
|
||||
lib: &FunctionsLib,
|
||||
stmt: &Stmt,
|
||||
@ -2012,8 +2044,8 @@ fn run_builtin_binary_op(
|
||||
}
|
||||
|
||||
if args_type == TypeId::of::<INT>() {
|
||||
let x = x.downcast_ref::<INT>().unwrap().clone();
|
||||
let y = y.downcast_ref::<INT>().unwrap().clone();
|
||||
let x = *x.downcast_ref::<INT>().unwrap();
|
||||
let y = *y.downcast_ref::<INT>().unwrap();
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
match op {
|
||||
@ -2054,8 +2086,8 @@ fn run_builtin_binary_op(
|
||||
_ => (),
|
||||
}
|
||||
} else if args_type == TypeId::of::<bool>() {
|
||||
let x = x.downcast_ref::<bool>().unwrap().clone();
|
||||
let y = y.downcast_ref::<bool>().unwrap().clone();
|
||||
let x = *x.downcast_ref::<bool>().unwrap();
|
||||
let y = *y.downcast_ref::<bool>().unwrap();
|
||||
|
||||
match op {
|
||||
"&" => return Ok(Some((x && y).into())),
|
||||
@ -2079,8 +2111,8 @@ fn run_builtin_binary_op(
|
||||
_ => (),
|
||||
}
|
||||
} else if args_type == TypeId::of::<char>() {
|
||||
let x = x.downcast_ref::<char>().unwrap().clone();
|
||||
let y = y.downcast_ref::<char>().unwrap().clone();
|
||||
let x = *x.downcast_ref::<char>().unwrap();
|
||||
let y = *y.downcast_ref::<char>().unwrap();
|
||||
|
||||
match op {
|
||||
"==" => return Ok(Some((x == y).into())),
|
||||
@ -2102,8 +2134,8 @@ fn run_builtin_binary_op(
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
{
|
||||
if args_type == TypeId::of::<FLOAT>() {
|
||||
let x = x.downcast_ref::<FLOAT>().unwrap().clone();
|
||||
let y = y.downcast_ref::<FLOAT>().unwrap().clone();
|
||||
let x = *x.downcast_ref::<FLOAT>().unwrap();
|
||||
let y = *y.downcast_ref::<FLOAT>().unwrap();
|
||||
|
||||
match op {
|
||||
"+" => return Ok(Some((x + y).into())),
|
||||
@ -2142,7 +2174,7 @@ fn run_builtin_op_assignment(
|
||||
|
||||
if args_type == TypeId::of::<INT>() {
|
||||
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"))]
|
||||
match op {
|
||||
@ -2178,7 +2210,7 @@ fn run_builtin_op_assignment(
|
||||
}
|
||||
} else if args_type == TypeId::of::<bool>() {
|
||||
let x = x.downcast_mut::<bool>().unwrap();
|
||||
let y = y.downcast_ref::<bool>().unwrap().clone();
|
||||
let y = *y.downcast_ref::<bool>().unwrap();
|
||||
|
||||
match op {
|
||||
"&=" => return Ok(Some(*x = *x && y)),
|
||||
@ -2199,7 +2231,7 @@ fn run_builtin_op_assignment(
|
||||
{
|
||||
if args_type == TypeId::of::<FLOAT>() {
|
||||
let x = x.downcast_mut::<FLOAT>().unwrap();
|
||||
let y = y.downcast_ref::<FLOAT>().unwrap().clone();
|
||||
let y = *y.downcast_ref::<FLOAT>().unwrap();
|
||||
|
||||
match op {
|
||||
"+=" => return Ok(Some(*x += y)),
|
||||
|
111
src/module.rs
111
src/module.rs
@ -2,7 +2,7 @@
|
||||
|
||||
use crate::any::{Dynamic, Variant};
|
||||
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::parser::{
|
||||
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
|
||||
///
|
||||
@ -388,7 +388,30 @@ impl Module {
|
||||
/// use rhai::Module;
|
||||
///
|
||||
/// 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)
|
||||
/// });
|
||||
/// 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,
|
||||
/// returning a hash key.
|
||||
///
|
||||
/// If there is a similar existing Rust function, it is replaced.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Module;
|
||||
/// use rhai::{Module, ImmutableString};
|
||||
///
|
||||
/// 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)
|
||||
/// });
|
||||
/// 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.
|
||||
///
|
||||
/// If there is a similar existing Rust function, it is replaced.
|
||||
@ -456,10 +534,10 @@ impl Module {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Module;
|
||||
/// use rhai::{Module, ImmutableString};
|
||||
///
|
||||
/// 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)
|
||||
/// });
|
||||
/// assert!(module.get_fn(hash).is_some());
|
||||
@ -499,10 +577,10 @@ impl Module {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Module;
|
||||
/// use rhai::{Module, ImmutableString};
|
||||
///
|
||||
/// 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)
|
||||
/// });
|
||||
/// assert!(module.get_fn(hash).is_some());
|
||||
@ -541,10 +619,10 @@ impl Module {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Module;
|
||||
/// use rhai::{Module, ImmutableString};
|
||||
///
|
||||
/// 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)
|
||||
/// });
|
||||
/// 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.
|
||||
///
|
||||
/// If there is a similar existing Rust function, it is replaced.
|
||||
@ -591,10 +669,10 @@ impl Module {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Module;
|
||||
/// use rhai::{Module, ImmutableString};
|
||||
///
|
||||
/// 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)
|
||||
/// });
|
||||
/// assert!(module.get_fn(hash).is_some());
|
||||
@ -1044,6 +1122,9 @@ mod stat {
|
||||
|
||||
/// 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
|
||||
///
|
||||
/// ```
|
||||
|
@ -122,7 +122,7 @@ fn call_fn_with_constant_arguments(
|
||||
state
|
||||
.engine
|
||||
.call_fn_raw(
|
||||
None,
|
||||
&mut Scope::new(),
|
||||
&mut Default::default(),
|
||||
state.lib,
|
||||
fn_name,
|
||||
@ -138,7 +138,7 @@ fn call_fn_with_constant_arguments(
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
// if expr { 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.
|
||||
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
|
||||
const DONT_EVAL_KEYWORDS: [&str; 3] = [KEYWORD_PRINT, KEYWORD_DEBUG, KEYWORD_EVAL];
|
||||
|
||||
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 )
|
||||
Expr::Stmt(x) => match optimize_stmt(x.0, state, true) {
|
||||
// ( 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));
|
||||
|
||||
#[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| {
|
||||
list.clear();
|
||||
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_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
|
||||
lib.set_fn_1("to_float", |x: INT| 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"))]
|
||||
{
|
||||
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_1("len", |s: ImmutableString| Ok(s.chars().count() as INT));
|
||||
lib.set_fn_2(
|
||||
lib.set_fn_1_mut("len", |s: &mut ImmutableString| Ok(s.chars().count() as INT));
|
||||
|
||||
#[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",
|
||||
|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",
|
||||
|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",
|
||||
|s: ImmutableString, ch: char, start: INT| {
|
||||
|s: &mut ImmutableString, ch: char, start: INT| {
|
||||
let start = if start < 0 {
|
||||
0
|
||||
} 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))
|
||||
},
|
||||
);
|
||||
lib.set_fn_2(
|
||||
lib.set_fn_2_mut(
|
||||
"index_of",
|
||||
|s: ImmutableString, ch: char| {
|
||||
|s: &mut ImmutableString, ch: char| {
|
||||
Ok(s.find(ch)
|
||||
.map(|index| s[0..index].chars().count() as INT)
|
||||
.unwrap_or(-1 as INT))
|
||||
},
|
||||
);
|
||||
lib.set_fn_3(
|
||||
lib.set_fn_3_mut(
|
||||
"index_of",
|
||||
|s: ImmutableString, find: ImmutableString, start: INT| {
|
||||
|s: &mut ImmutableString, find: ImmutableString, start: INT| {
|
||||
let start = if start < 0 {
|
||||
0
|
||||
} 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))
|
||||
},
|
||||
);
|
||||
lib.set_fn_2(
|
||||
lib.set_fn_2_mut(
|
||||
"index_of",
|
||||
|s: ImmutableString, find: ImmutableString| {
|
||||
|s: &mut ImmutableString, find: ImmutableString| {
|
||||
Ok(s.find(find.as_str())
|
||||
.map(|index| s[0..index].chars().count() as INT)
|
||||
.unwrap_or(-1 as INT))
|
||||
|
@ -10,6 +10,9 @@ use crate::token::Position;
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
use crate::stdlib::time::Instant;
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
use crate::parser::FLOAT;
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
|
||||
// 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("!=", ne::<Instant>);
|
||||
|
||||
lib.set_fn_1(
|
||||
"elapsed",
|
||||
|timestamp: Instant| {
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
return Ok(timestamp.elapsed().as_secs_f64());
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
fn elapsed (timestamp: &mut Instant) -> Result<FLOAT, Box<EvalAltResult>> {
|
||||
Ok(timestamp.elapsed().as_secs_f64())
|
||||
}
|
||||
|
||||
#[cfg(feature = "no_float")]
|
||||
{
|
||||
let seconds = timestamp.elapsed().as_secs();
|
||||
#[cfg(feature = "no_float")]
|
||||
fn elapsed (timestamp: &mut Instant) -> Result<INT, Box<EvalAltResult>> {
|
||||
let seconds = timestamp.elapsed().as_secs();
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
{
|
||||
if seconds > (MAX_INT as u64) {
|
||||
return Err(Box::new(EvalAltResult::ErrorArithmetic(
|
||||
format!("Integer overflow for timestamp.elapsed(): {}", seconds),
|
||||
Position::none(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
return Ok(seconds as INT);
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
{
|
||||
if seconds > (MAX_INT as u64) {
|
||||
return Err(Box::new(EvalAltResult::ErrorArithmetic(
|
||||
format!("Integer overflow for timestamp.elapsed: {}", seconds),
|
||||
Position::none(),
|
||||
)));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
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,
|
||||
format,
|
||||
iter::{empty, Peekable},
|
||||
mem,
|
||||
num::NonZeroUsize,
|
||||
ops::{Add, Deref, DerefMut},
|
||||
string::{String, ToString},
|
||||
@ -391,6 +392,8 @@ pub enum Expr {
|
||||
Property(Box<((String, String, String), Position)>),
|
||||
/// { stmt }
|
||||
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)
|
||||
/// 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`.
|
||||
@ -441,6 +444,8 @@ impl Expr {
|
||||
/// Panics when the expression is not constant.
|
||||
pub fn get_constant_value(&self) -> Dynamic {
|
||||
match self {
|
||||
Self::Expr(x) => x.get_constant_value(),
|
||||
|
||||
Self::IntegerConstant(x) => x.0.into(),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Self::FloatConstant(x) => x.0.into(),
|
||||
@ -475,6 +480,8 @@ impl Expr {
|
||||
/// Panics when the expression is not constant.
|
||||
pub fn get_constant_str(&self) -> String {
|
||||
match self {
|
||||
Self::Expr(x) => x.get_constant_str(),
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Self::FloatConstant(x) => x.0.to_string(),
|
||||
|
||||
@ -494,6 +501,8 @@ impl Expr {
|
||||
/// Get the `Position` of the expression.
|
||||
pub fn position(&self) -> Position {
|
||||
match self {
|
||||
Self::Expr(x) => x.position(),
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Self::FloatConstant(x) => x.1,
|
||||
|
||||
@ -519,6 +528,11 @@ impl Expr {
|
||||
/// Override the `Position` of the expression.
|
||||
pub(crate) fn set_position(mut self, new_pos: Position) -> Self {
|
||||
match &mut self {
|
||||
Self::Expr(ref mut x) => {
|
||||
let expr = mem::take(x);
|
||||
*x = Box::new(expr.set_position(new_pos));
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Self::FloatConstant(x) => x.1 = new_pos,
|
||||
|
||||
@ -550,6 +564,8 @@ impl Expr {
|
||||
/// A pure expression has no side effects.
|
||||
pub fn is_pure(&self) -> bool {
|
||||
match self {
|
||||
Self::Expr(x) => x.is_pure(),
|
||||
|
||||
Self::Array(x) => x.0.iter().all(Self::is_pure),
|
||||
|
||||
Self::Index(x) | Self::And(x) | Self::Or(x) | Self::In(x) => {
|
||||
@ -568,6 +584,8 @@ impl Expr {
|
||||
/// Is the expression a constant?
|
||||
pub fn is_constant(&self) -> bool {
|
||||
match self {
|
||||
Self::Expr(x) => x.is_constant(),
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Self::FloatConstant(_) => true,
|
||||
|
||||
@ -598,6 +616,8 @@ impl Expr {
|
||||
/// Is a particular token allowed as a postfix operator to this expression?
|
||||
pub fn is_valid_postfix(&self, token: &Token) -> bool {
|
||||
match self {
|
||||
Self::Expr(x) => x.is_valid_postfix(token),
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Self::FloatConstant(_) => false,
|
||||
|
||||
@ -996,7 +1016,7 @@ fn parse_index_chain<'a>(
|
||||
(Token::LeftBracket, _) => {
|
||||
let idx_pos = eat_token(input, Token::LeftBracket);
|
||||
// Recursively parse the indexing chain, right-binding each
|
||||
let idx = parse_index_chain(
|
||||
let idx_expr = parse_index_chain(
|
||||
input,
|
||||
state,
|
||||
idx_expr,
|
||||
@ -1005,10 +1025,20 @@ fn parse_index_chain<'a>(
|
||||
allow_stmt_expr,
|
||||
)?;
|
||||
// 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
|
||||
_ => 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)),
|
||||
@ -1732,37 +1762,34 @@ fn parse_binary_op<'a>(
|
||||
let cmp_def = Some(false.into());
|
||||
let op = op_token.syntax();
|
||||
let hash = calc_fn_hash(empty(), &op, 2, empty());
|
||||
let op = (op, true, pos);
|
||||
|
||||
let mut args = StaticVec::new();
|
||||
args.push(root);
|
||||
args.push(rhs);
|
||||
|
||||
root = match op_token {
|
||||
Token::Plus => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
|
||||
Token::Minus => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
|
||||
Token::Multiply => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
|
||||
Token::Divide => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
|
||||
Token::Plus
|
||||
| Token::Minus
|
||||
| Token::Multiply
|
||||
| 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))),
|
||||
Token::RightShift => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
|
||||
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))),
|
||||
// '!=' defaults to true when passed invalid operands
|
||||
Token::NotEqualsTo => Expr::FnCall(Box::new((op, None, hash, args, Some(true.into())))),
|
||||
|
||||
// Comparison operators default to false when passed invalid operands
|
||||
Token::EqualsTo => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, cmp_def))),
|
||||
Token::NotEqualsTo => {
|
||||
Expr::FnCall(Box::new(((op, true, pos), None, hash, args, cmp_def)))
|
||||
}
|
||||
Token::LessThan => Expr::FnCall(Box::new(((op, true, pos), 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::EqualsTo
|
||||
| Token::LessThan
|
||||
| Token::LessThanEqualsTo
|
||||
| Token::GreaterThan
|
||||
| Token::GreaterThanEqualsTo => Expr::FnCall(Box::new((op, None, hash, args, cmp_def))),
|
||||
|
||||
Token::Or => {
|
||||
let rhs = args.pop();
|
||||
@ -1774,10 +1801,6 @@ fn parse_binary_op<'a>(
|
||||
let current_lhs = args.pop();
|
||||
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 => {
|
||||
let rhs = args.pop();
|
||||
let current_lhs = args.pop();
|
||||
|
@ -26,7 +26,7 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
|
||||
let y = [4, 5];
|
||||
x.append(y);
|
||||
|
||||
x.len() + r
|
||||
x.len + r
|
||||
"
|
||||
)?,
|
||||
14
|
||||
|
@ -48,7 +48,7 @@ fn test_for_object() -> Result<(), Box<EvalAltResult>> {
|
||||
sum += value;
|
||||
}
|
||||
|
||||
keys.len() + sum
|
||||
keys.len + sum
|
||||
"#;
|
||||
|
||||
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 } let a = 40; add(a, 2); a")?,
|
||||
40
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
assert_eq!(
|
||||
engine.eval::<INT>("fn add(x, n) { x + n } let x = 40; x.add(2)")?,
|
||||
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);
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
@ -21,5 +32,11 @@ fn test_functions() -> Result<(), Box<EvalAltResult>> {
|
||||
42
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
assert_eq!(
|
||||
engine.eval::<INT>("fn mul2(x) { x *= 2; } let x = 21; x.mul2(); x")?,
|
||||
21
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ fn test_method_call() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<TestStruct>("let x = new_ts(); update(x, 1000); x")?,
|
||||
TestStruct { x: 1 }
|
||||
TestStruct { x: 1001 }
|
||||
);
|
||||
|
||||
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!"; "hey" in y"#)?);
|
||||
|
||||
#[cfg(not(feature = "no_stdlib"))]
|
||||
assert_eq!(engine.eval::<String>(r#""foo" + 123"#)?, "foo123");
|
||||
|
||||
#[cfg(not(feature = "no_stdlib"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
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_stdlib"))]
|
||||
assert_eq!(engine.eval::<String>(r#""foo" + 123.4556"#)?, "foo123.4556");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_stdlib"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
#[test]
|
||||
fn test_string_substring() -> Result<(), Box<EvalAltResult>> {
|
||||
|
@ -1,4 +1,3 @@
|
||||
#![cfg(not(feature = "no_stdlib"))]
|
||||
#![cfg(not(feature = "no_std"))]
|
||||
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
|
Loading…
Reference in New Issue
Block a user