Merge branch 'master' into plugins

This commit is contained in:
Stephen Chung 2020-05-30 13:51:54 +08:00
commit 6924b62939
25 changed files with 566 additions and 327 deletions

133
README.md
View File

@ -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 '`_`').
@ -1290,15 +1313,15 @@ 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` |
| Rounding | `floor`, `ceiling`, `round`, `int`, `fraction` methods and properties |
| Conversion | [`to_int`] |
| Testing | `is_nan`, `is_finite`, `is_infinite` |
| 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,11 +1411,11 @@ 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 |
| ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
| `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 |
@ -1408,14 +1431,14 @@ The following standard methods (defined in the [`MoreStringPackage`](#packages)
```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,10 +1486,10 @@ 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 |
@ -1474,7 +1497,7 @@ The following methods (defined in the [`BasicArrayPackage`](#packages) but exclu
| `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 |
| `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) |
@ -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:
@ -1723,8 +1746,8 @@ 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 |
| ------------------ | ---------------------------------- | -------------------------------------------------------- |
| `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
@ -2008,7 +2034,8 @@ 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 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
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

View File

@ -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

View File

@ -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)
"#;

View File

@ -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)
"#;

View File

@ -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);

View File

@ -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.");

View File

@ -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!");

View File

@ -10,4 +10,4 @@ while x > 0 {
x -= 1;
}
print("Finished. Run time = " + now.elapsed() + " seconds.");
print("Finished. Run time = " + now.elapsed + " seconds.");

View File

@ -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?'

View File

@ -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 {

View File

@ -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());

View File

@ -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,9 +786,6 @@ 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();
// Put arguments into scope as variables
@ -777,13 +807,9 @@ impl Engine {
.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,
)))
}
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,
@ -795,46 +821,7 @@ impl Engine {
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)),

View File

@ -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
///
/// ```

View File

@ -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 ) -> ()

View File

@ -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(())

View File

@ -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));

View File

@ -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))

View File

@ -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());
fn elapsed (timestamp: &mut Instant) -> Result<FLOAT, Box<EvalAltResult>> {
Ok(timestamp.elapsed().as_secs_f64())
}
#[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),
format!("Integer overflow for timestamp.elapsed: {}", seconds),
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);
});

View File

@ -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,12 +1025,22 @@ 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
_ => {
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)),
(_, pos) => Err(PERR::MissingToken(
Token::RightBracket.into(),
@ -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();

View File

@ -26,7 +26,7 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
let y = [4, 5];
x.append(y);
x.len() + r
x.len + r
"
)?,
14

View File

@ -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);

View File

@ -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(())
}

View File

@ -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(())

View File

@ -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>> {

View File

@ -1,4 +1,3 @@
#![cfg(not(feature = "no_stdlib"))]
#![cfg(not(feature = "no_std"))]
use rhai::{Engine, EvalAltResult, INT};