Merge branch 'master' into plugins

This commit is contained in:
Stephen Chung 2020-07-28 11:08:57 +08:00
commit 0c703c0361
25 changed files with 551 additions and 358 deletions

View File

@ -27,12 +27,11 @@ Standard features
* Easily [call a script-defined function](https://schungx.github.io/rhai/engine/call-fn.html) from Rust.
* Fairly low compile-time overhead.
* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM).
* Relatively little `unsafe` code (yes there are some for performance reasons, and most `unsafe` code is limited to
one single source file, all with names starting with `"unsafe_"`).
* Relatively little `unsafe` code (yes there are some for performance reasons).
* Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature).
* [Function overloading](https://schungx.github.io/rhai/language/overload.html).
* [Operator overloading](https://schungx.github.io/rhai/rust/operators.html).
* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html).
* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html) with additional support for [currying](https://schungx.github.io/rhai/language/fn-curry.html).
* Some support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.html).
* Organize code base with dynamically-loadable [modules](https://schungx.github.io/rhai/language/modules.html).
* Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature).

View File

@ -27,6 +27,7 @@ Breaking changes
* Language keywords are now _reserved_ (even when disabled) and they can no longer be used as variable names.
* Function signature for defining custom syntax is simplified.
* `Engine::register_raw_fn_XXX` API shortcuts are removed.
* `PackagesCollection::get_fn`, `PackagesCollection::contains_fn`, `Module::get_fn` and `Module::contains_fn` now take an additional `public_only` parameter indicating whether only public functions are accepted.
Housekeeping
------------

View File

@ -35,23 +35,20 @@ Dynamic
* Organize code base with dynamically-loadable [modules].
* Dynamic dispatch via [function pointers].
* Dynamic dispatch via [function pointers] with additional support for [currying].
* Some support for [object-oriented programming (OOP)][OOP].
* Serialization/deserialization support via [`serde`].
Safe
----
* Relatively little `unsafe` code (yes there are some for performance reasons, and most `unsafe` code is limited to
one single source file, all with names starting with `"unsafe_"`).
* Relatively little `unsafe` code (yes there are some for performance reasons).
* Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`).
Rugged
------
* Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`).
* Protected against malicious attacks (such as [stack-overflow][maximum call stack depth], [over-sized data][maximum length of strings], and [runaway scripts][maximum number of operations] etc.) that may come from untrusted third-party user-land scripts.
* Track script evaluation [progress] and manually terminate a script run.
@ -61,6 +58,8 @@ Flexible
* Re-entrant scripting [`Engine`] can be made `Send + Sync` (via the [`sync`] feature).
* Serialization/deserialization support via [`serde`](https://crates.io/crates/serde).
* Support for [minimal builds] by excluding unneeded language [features].
* Supports [most build targets](targets.md) including `no-std` and [WASM].

View File

@ -59,8 +59,10 @@ Reserved Keywords
| `package` | Package |
| `spawn` | Threading |
| `go` | Threading |
| `shared` | Threading |
| `await` | Async |
| `async` | Async |
| `sync` | Async |
| `yield` | Async |
| `default` | Special value |
| `void` | Special value |

View File

@ -69,17 +69,22 @@ The only practical way to ensure that a function is a correct one is to use [mod
i.e. define the function in a separate module and then [`import`] it:
```rust
message.rhai:
----------------
| message.rhai |
----------------
fn message() { "Hello!" }
fn message() { "Hello!" }
script.rhai:
fn say_hello() {
import "message" as msg;
print(msg::message());
}
say_hello();
---------------
| script.rhai |
---------------
fn say_hello() {
import "message" as msg;
print(msg::message());
}
say_hello();
```
@ -94,18 +99,23 @@ defined _within the script_. When called later, those functions will be searche
current global namespace and may not be found.
```rust
greeting.rhai:
-----------------
| greeting.rhai |
-----------------
fn message() { "Hello!" };
fn message() { "Hello!" };
fn say_hello() { print(message()); }
fn say_hello() { print(message()); }
say_hello(); // 'message' is looked up in the global namespace
say_hello(); // 'message' is looked up in the global namespace
script.rhai:
import "greeting" as g;
g::say_hello(); // <- error: function not found - 'message'
---------------
| script.rhai |
---------------
import "greeting" as g;
g::say_hello(); // <- error: function not found - 'message'
```
In the example above, although the module `greeting.rhai` loads fine (`"Hello!"` is printed),
@ -118,24 +128,29 @@ as possible and avoid cross-calling them from each other. A [function pointer]
to call another function within a module-defined function:
```rust
greeting.rhai:
-----------------
| greeting.rhai |
-----------------
fn message() { "Hello!" };
fn message() { "Hello!" };
fn say_hello(msg_func) { // 'msg_func' is a function pointer
print(msg_func.call()); // call via the function pointer
}
fn say_hello(msg_func) { // 'msg_func' is a function pointer
print(msg_func.call()); // call via the function pointer
}
say_hello(); // 'message' is looked up in the global namespace
say_hello(); // 'message' is looked up in the global namespace
script.rhai:
import "greeting" as g;
---------------
| script.rhai |
---------------
fn my_msg() {
import "greeting" as g; // <- must import again here...
g::message() // <- ... otherwise will not find module 'g'
}
import "greeting" as g;
g::say_hello(Fn("my_msg")); // prints 'Hello!'
fn my_msg() {
import "greeting" as g; // <- must import again here...
g::message() // <- ... otherwise will not find module 'g'
}
g::say_hello(Fn("my_msg")); // prints 'Hello!'
```

View File

@ -16,7 +16,7 @@ fn sub(x, y,) { // trailing comma in parameters list is OK
add(2, 3) == 5;
sub(2, 3,) == -1; // trailing comma in arguments list is OK
sub(2, 3,) == -1; // trailing comma in arguments list is OK
```

View File

@ -5,21 +5,21 @@ Keywords
The following are reserved keywords in Rhai:
| Active keywords | Reserved keywords | Usage | Inactive under feature |
| ------------------------------------------------- | ---------------------------------------- | --------------------- | :--------------------: |
| `true`, `false` | | Boolean constants | |
| `let`, `const` | `var`, `static` | Variable declarations | |
| `if`, `else` | `then`, `goto`, `exit` | Control flow | |
| | `switch`, `match`, `case` | Matching | |
| `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | Looping | |
| `fn`, `private` | `public`, `new` | Functions | [`no_function`] |
| `return` | | Return values | |
| `throw` | `try`, `catch` | Throw exceptions | |
| `import`, `export`, `as` | `use`, `with`, `module`, `package` | Modules/packages | [`no_module`] |
| `Fn`, `call`, `curry` | | Function pointers | |
| | `spawn`, `go`, `async`, `await`, `yield` | Threading/async | |
| `type_of`, `print`, `debug`, `eval` | | Special functions | |
| | `default`, `void`, `null`, `nil` | Special values | |
| Active keywords | Reserved keywords | Usage | Inactive under feature |
| ------------------------------------------------- | ---------------------------------------------------------- | --------------------- | :--------------------: |
| `true`, `false` | | Boolean constants | |
| `let`, `const` | `var`, `static` | Variable declarations | |
| `if`, `else` | `then`, `goto`, `exit` | Control flow | |
| | `switch`, `match`, `case` | Matching | |
| `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | Looping | |
| `fn`, `private` | `public`, `new` | Functions | [`no_function`] |
| `return` | | Return values | |
| `throw` | `try`, `catch` | Throw exceptions | |
| `import`, `export`, `as` | `use`, `with`, `module`, `package` | Modules/packages | [`no_module`] |
| `Fn`, `call`, `curry` | | Function pointers | |
| | `spawn`, `go`, `shared`, `sync`, `async`, `await`, `yield` | Threading/async | |
| `type_of`, `print`, `debug`, `eval` | | Special functions | |
| | `default`, `void`, `null`, `nil` | Special values | |
Keywords cannot become the name of a [function] or [variable], even when they are disabled.

View File

@ -37,12 +37,30 @@ array[0].update(); // <- call in method-call style will update 'a'
```
`&mut` is Efficient
------------------
Number of Parameters
--------------------
Native Rust methods registered with an [`Engine`] take _one additional parameter_ more than
an equivalent method coded in script, where the object is accessed via the `this` pointer instead.
The following table illustrates the differences:
| Function type | Parameters | Object reference | Function signature |
| :-----------: | :--------: | :--------------------: | :-----------------------------------------------------: |
| Native Rust | _n_ + 1 | First `&mut` parameter | `fn method<T, U, V>`<br/>`(obj: &mut T, x: U, y: V) {}` |
| Rhai script | _n_ | `this` | `fn method(x, y) {}` |
`&mut` is Efficient (Except for `ImmutableString`)
------------------------------------------------
Using a `&mut` first parameter is highly encouraged when using types that are expensive to clone,
even when the intention is not to mutate that argument, because it avoids cloning that argument value.
For example, the `len` method of an [array] has the signature: `Fn(&mut Array) -> INT`.
The array itself is not modified in any way, but using a `&mut` parameter avoids a cloning that would
otherwise have happened if the signature were `Fn(Array) -> INT`.
For primary types that are cheap to clone (e.g. those that implement `Copy`),
including `ImmutableString`, this is not necessary.

View File

@ -63,7 +63,9 @@ cause a stack overflow in the [`Engine`], unless stopped by setting a limit for
For instance, importing itself always causes an infinite recursion:
```rust
// This file is 'hello.rhai'
--------------
| hello.rhai |
--------------
import "hello" as foo; // import itself - infinite recursion!
@ -73,11 +75,18 @@ foo::do_something();
Modules cross-referencing also cause infinite recursion:
```rust
// This file is 'hello.rhai' - references 'world.rhai'
--------------
| hello.rhai |
--------------
import "world" as foo;
foo::do_something();
// This file is 'world.rhai' - references 'hello.rhai'
--------------
| world.rhai |
--------------
import "hello" as bar;
bar::do_something_else();
```

View File

@ -9,15 +9,15 @@ and _number_ of parameters, but not parameter _types_ since all parameters are t
New definitions _overwrite_ previous definitions of the same name and number of parameters.
```rust
fn foo(x,y,z) { print("Three!!! " + x + "," + y + "," + z) }
fn foo(x,y,z) { print("Three!!! " + x + "," + y + "," + z); }
fn foo(x) { print("One! " + x) }
fn foo(x) { print("One! " + x); }
fn foo(x,y) { print("Two! " + x + "," + y) }
fn foo(x,y) { print("Two! " + x + "," + y); }
fn foo() { print("None.") }
fn foo() { print("None."); }
fn foo(x) { print("HA! NEW ONE! " + x) } // overwrites previous definition
fn foo(x) { print("HA! NEW ONE! " + x); } // overwrites previous definition
foo(1,2,3); // prints "Three!!! 1,2,3"

View File

@ -125,8 +125,7 @@ engine.register_raw_fn(
let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer
// Use 'FnPtr::call_dynamic' to call the function pointer.
// Beware, only script-defined functions are supported by 'FnPtr::call_dynamic'.
// If it is a native Rust function, directly call it here in Rust instead!
// Beware, private script-defined functions will not be found.
fp.call_dynamic(engine, lib, Some(this_ptr), [value])
},
);

View File

@ -24,7 +24,7 @@ more control over what a script can (or cannot) do.
| `no_function` | Disable script-defined [functions]. |
| `no_module` | Disable loading external [modules]. |
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
| `serde` | Enable serialization/deserialization via [`serde`]. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. |
| `serde` | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. |
| `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. |

View File

@ -72,15 +72,17 @@ fn main() {
println!("==============");
print_help();
loop {
'main_loop: loop {
print!("rhai> ");
stdout().flush().expect("couldn't flush stdout");
input.clear();
loop {
if let Err(err) = stdin().read_line(&mut input) {
panic!("input error: {}", err);
match stdin().read_line(&mut input) {
Ok(0) => break 'main_loop,
Ok(_) => (),
Err(err) => panic!("input error: {}", err),
}
let line = input.as_str().trim_end();

View File

@ -19,7 +19,7 @@ use crate::utils::StaticVec;
use crate::any::Variant;
#[cfg(not(feature = "no_function"))]
use crate::parser::{FnAccess, ScriptFnDef};
use crate::parser::ScriptFnDef;
#[cfg(not(feature = "no_module"))]
use crate::module::ModuleResolver;
@ -228,11 +228,6 @@ impl<T: Into<Dynamic>> From<T> for Target<'_> {
/// [INTERNALS] A type that holds all the current states of the Engine.
/// Exported under the `internals` feature only.
///
/// # Safety
///
/// This type uses some unsafe code, mainly for avoiding cloning of local variable names via
/// direct lifetime casting.
///
/// ## WARNING
///
/// This type is volatile and may change.
@ -264,19 +259,15 @@ pub fn get_script_function_by_signature<'a>(
module: &'a Module,
name: &str,
params: usize,
public_only: bool,
pub_only: bool,
) -> Option<&'a ScriptFnDef> {
// Qualifiers (none) + function name + number of arguments.
let hash_script = calc_fn_hash(empty(), name, params, empty());
let func = module.get_fn(hash_script)?;
if !func.is_script() {
return None;
}
let fn_def = func.get_fn_def();
match fn_def.access {
FnAccess::Private if public_only => None,
FnAccess::Private | FnAccess::Public => Some(&fn_def),
let func = module.get_fn(hash_script, pub_only)?;
if func.is_script() {
Some(func.get_fn_def())
} else {
None
}
}
@ -695,8 +686,8 @@ impl Engine {
let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val];
self.exec_fn_call(
state, lib, FN_IDX_SET, true, 0, args, is_ref, true, None,
level,
state, lib, FN_IDX_SET, true, 0, args, is_ref, true, false,
None, level,
)
.or_else(|err| match *err {
// If there is no index setter, no need to set it back because the indexer is read-only
@ -719,8 +710,8 @@ impl Engine {
let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val];
self.exec_fn_call(
state, lib, FN_IDX_SET, true, 0, args, is_ref, true, None,
level,
state, lib, FN_IDX_SET, true, 0, args, is_ref, true, false,
None, level,
)?;
}
// Error
@ -741,7 +732,12 @@ impl Engine {
match rhs {
// xxx.fn_name(arg_expr_list)
Expr::FnCall(x) if x.1.is_none() => {
self.make_method_call(state, lib, target, rhs, idx_val, level)
let ((name, native, pos), _, hash, _, def_val) = x.as_ref();
self.make_method_call(
state, lib, name, *hash, target, idx_val, *def_val, *native, false,
level,
)
.map_err(|err| err.new_position(*pos))
}
// xxx.module::fn_name(...) - syntax error
Expr::FnCall(_) => unreachable!(),
@ -770,7 +766,8 @@ impl Engine {
let ((_, _, setter), pos) = x.as_ref();
let mut args = [target.as_mut(), _new_val.as_mut().unwrap()];
self.exec_fn_call(
state, lib, setter, true, 0, &mut args, is_ref, true, None, level,
state, lib, setter, true, 0, &mut args, is_ref, true, false, None,
level,
)
.map(|(v, _)| (v, true))
.map_err(|err| err.new_position(*pos))
@ -780,7 +777,8 @@ impl Engine {
let ((_, getter, _), pos) = x.as_ref();
let mut args = [target.as_mut()];
self.exec_fn_call(
state, lib, getter, true, 0, &mut args, is_ref, true, None, level,
state, lib, getter, true, 0, &mut args, is_ref, true, false, None,
level,
)
.map(|(v, _)| (v, false))
.map_err(|err| err.new_position(*pos))
@ -791,15 +789,19 @@ impl Engine {
let mut val = match sub_lhs {
Expr::Property(p) => {
let ((prop, _, _), _) = p.as_ref();
let ((prop, _, _), pos) = p.as_ref();
let index = prop.clone().into();
self.get_indexed_mut(state, lib, target, index, *pos, false, level)?
}
// {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr
Expr::FnCall(x) if x.1.is_none() => {
let (val, _) = self.make_method_call(
state, lib, target, sub_lhs, idx_val, level,
)?;
let ((name, native, pos), _, hash, _, def_val) = x.as_ref();
let (val, _) = self
.make_method_call(
state, lib, name, *hash, target, idx_val, *def_val,
*native, false, level,
)
.map_err(|err| err.new_position(*pos))?;
val.into()
}
// {xxx:map}.module::fn_name(...) - syntax error
@ -816,18 +818,18 @@ impl Engine {
}
// xxx.sub_lhs[expr] | xxx.sub_lhs.expr
Expr::Index(x) | Expr::Dot(x) => {
let (sub_lhs, expr, pos) = x.as_ref();
let (sub_lhs, expr, _) = x.as_ref();
match sub_lhs {
// xxx.prop[expr] | xxx.prop.expr
Expr::Property(p) => {
let ((_, getter, setter), _) = p.as_ref();
let ((_, getter, setter), pos) = p.as_ref();
let arg_values = &mut [target.as_mut(), &mut Default::default()];
let args = &mut arg_values[..1];
let (mut val, updated) = self
.exec_fn_call(
state, lib, getter, true, 0, args, is_ref, true, None,
level,
state, lib, getter, true, 0, args, is_ref, true, false,
None, level,
)
.map_err(|err| err.new_position(*pos))?;
@ -847,7 +849,7 @@ impl Engine {
arg_values[1] = val;
self.exec_fn_call(
state, lib, setter, true, 0, arg_values, is_ref, true,
None, level,
false, None, level,
)
.or_else(
|err| match *err {
@ -864,9 +866,13 @@ impl Engine {
}
// xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr
Expr::FnCall(x) if x.1.is_none() => {
let (mut val, _) = self.make_method_call(
state, lib, target, sub_lhs, idx_val, level,
)?;
let ((name, native, pos), _, hash, _, def_val) = x.as_ref();
let (mut val, _) = self
.make_method_call(
state, lib, name, *hash, target, idx_val, *def_val,
*native, false, level,
)
.map_err(|err| err.new_position(*pos))?;
let val = &mut val;
let target = &mut val.into();
@ -1132,7 +1138,7 @@ impl Engine {
let type_name = val.type_name();
let args = &mut [val, &mut _idx];
self.exec_fn_call(
state, _lib, FN_IDX_GET, true, 0, args, is_ref, true, None, _level,
state, _lib, FN_IDX_GET, true, 0, args, is_ref, true, false, None, _level,
)
.map(|(v, _)| v.into())
.map_err(|err| match *err {
@ -1188,7 +1194,7 @@ impl Engine {
let (r, _) = self
.call_fn_raw(
&mut scope, mods, state, lib, op, hashes, args, false, false,
&mut scope, mods, state, lib, op, hashes, args, false, false, false,
def_value, level,
)
.map_err(|err| err.new_position(rhs.position()))?;
@ -1289,8 +1295,8 @@ impl Engine {
if let Some(CallableFunction::Method(func)) = self
.global_module
.get_fn(hash_fn)
.or_else(|| self.packages.get_fn(hash_fn))
.get_fn(hash_fn, false)
.or_else(|| self.packages.get_fn(hash_fn, false))
{
// Overriding exact implementation
func(self, lib, &mut [lhs_ptr, &mut rhs_val])?;
@ -1303,7 +1309,8 @@ impl Engine {
// Run function
let (value, _) = self
.exec_fn_call(
state, lib, op, true, hash, args, false, false, None, level,
state, lib, op, true, hash, args, false, false, false, None,
level,
)
.map_err(|err| err.new_position(*op_pos))?;
// Set value to LHS
@ -1331,9 +1338,11 @@ impl Engine {
&mut self.eval_expr(scope, mods, state, lib, this_ptr, lhs_expr, level)?,
&mut rhs_val,
];
self.exec_fn_call(state, lib, op, true, hash, args, false, false, None, level)
.map(|(v, _)| v)
.map_err(|err| err.new_position(*op_pos))?
self.exec_fn_call(
state, lib, op, true, hash, args, false, false, false, None, level,
)
.map(|(v, _)| v)
.map_err(|err| err.new_position(*op_pos))?
});
match lhs_expr {
@ -1403,7 +1412,7 @@ impl Engine {
let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref();
self.make_function_call(
scope, mods, state, lib, this_ptr, name, args_expr, *def_val, *hash, *native,
level,
false, level,
)
.map_err(|err| err.new_position(*pos))
}
@ -1481,6 +1490,12 @@ impl Engine {
}
/// Evaluate a statement
///
///
/// # Safety
///
/// This method uses some unsafe code, mainly for avoiding cloning of local variable names via
/// direct lifetime casting.
pub(crate) fn eval_stmt(
&self,
scope: &mut Scope,
@ -1524,7 +1539,7 @@ impl Engine {
// If-else statement
Stmt::IfThenElse(x) => {
let (expr, if_block, else_block) = x.as_ref();
let (expr, if_block, else_block, _) = x.as_ref();
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.as_bool()
@ -1542,7 +1557,7 @@ impl Engine {
// While loop
Stmt::While(x) => loop {
let (expr, body) = x.as_ref();
let (expr, body, _) = x.as_ref();
match self
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
@ -1568,8 +1583,8 @@ impl Engine {
},
// Loop statement
Stmt::Loop(body) => loop {
match self.eval_stmt(scope, mods, state, lib, this_ptr, body, level) {
Stmt::Loop(x) => loop {
match self.eval_stmt(scope, mods, state, lib, this_ptr, &x.0, level) {
Ok(_) => (),
Err(err) => match *err {
EvalAltResult::ErrorLoopBreak(false, _) => (),
@ -1581,7 +1596,7 @@ impl Engine {
// For loop
Stmt::For(x) => {
let (name, expr, stmt) = x.as_ref();
let (name, expr, stmt, _) = x.as_ref();
let iter_type = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
let tid = iter_type.type_id();
@ -1627,16 +1642,9 @@ impl Engine {
// Return value
Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => {
let expr = x.1.as_ref().unwrap();
Err(Box::new(EvalAltResult::Return(
self.eval_expr(
scope,
mods,
state,
lib,
this_ptr,
x.1.as_ref().unwrap(),
level,
)?,
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?,
(x.0).1,
)))
}
@ -1648,15 +1656,8 @@ impl Engine {
// Throw value
Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Exception => {
let val = self.eval_expr(
scope,
mods,
state,
lib,
this_ptr,
x.1.as_ref().unwrap(),
level,
)?;
let expr = x.1.as_ref().unwrap();
let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
Err(Box::new(EvalAltResult::ErrorRuntime(
val.take_string().unwrap_or_else(|_| "".into()),
(x.0).1,
@ -1672,23 +1673,16 @@ impl Engine {
// Let statement
Stmt::Let(x) if x.1.is_some() => {
let ((var_name, _), expr) = x.as_ref();
let val = self.eval_expr(
scope,
mods,
state,
lib,
this_ptr,
expr.as_ref().unwrap(),
level,
)?;
let ((var_name, _), expr, _) = x.as_ref();
let expr = expr.as_ref().unwrap();
let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state);
scope.push_dynamic_value(var_name, ScopeEntryType::Normal, val, false);
Ok(Default::default())
}
Stmt::Let(x) => {
let ((var_name, _), _) = x.as_ref();
let ((var_name, _), _, _) = x.as_ref();
let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state);
scope.push(var_name, ());
Ok(Default::default())
@ -1696,7 +1690,7 @@ impl Engine {
// Const statement
Stmt::Const(x) if x.1.is_constant() => {
let ((var_name, _), expr) = x.as_ref();
let ((var_name, _), expr, _) = x.as_ref();
let val = self.eval_expr(scope, mods, state, lib, this_ptr, &expr, level)?;
let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state);
scope.push_dynamic_value(var_name, ScopeEntryType::Constant, val, true);
@ -1709,7 +1703,7 @@ impl Engine {
// Import statement
#[cfg(not(feature = "no_module"))]
Stmt::Import(x) => {
let (expr, (name, _pos)) = x.as_ref();
let (expr, (name, _pos), _) = x.as_ref();
// Guard against too many modules
#[cfg(not(feature = "unchecked"))]
@ -1742,8 +1736,8 @@ impl Engine {
// Export statement
#[cfg(not(feature = "no_module"))]
Stmt::Export(list) => {
for ((id, id_pos), rename) in list.iter() {
Stmt::Export(x) => {
for ((id, id_pos), rename) in x.0.iter() {
// Mark scope variables as public
if let Some(index) = scope.get_index(id).map(|(i, _)| i) {
let alias = rename.as_ref().map(|(n, _)| n).unwrap_or_else(|| id);

View File

@ -125,6 +125,7 @@ impl Engine {
args: &mut FnCallArgs,
is_ref: bool,
_is_method: bool,
pub_only: bool,
def_val: Option<bool>,
_level: usize,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
@ -150,14 +151,14 @@ impl Engine {
// Then search packages
// NOTE: We skip script functions for global_module and packages, and native functions for lib
let func = if !native_only {
lib.get_fn(hash_script) //.or_else(|| lib.get_fn(hash_fn))
lib.get_fn(hash_script, pub_only) //.or_else(|| lib.get_fn(hash_fn, pub_only))
} else {
None
}
//.or_else(|| self.global_module.get_fn(hash_script))
.or_else(|| self.global_module.get_fn(hash_fn))
//.or_else(|| self.packages.get_fn(hash_script))
.or_else(|| self.packages.get_fn(hash_fn));
//.or_else(|| self.global_module.get_fn(hash_script, pub_only))
.or_else(|| self.global_module.get_fn(hash_fn, pub_only))
//.or_else(|| self.packages.get_fn(hash_script, pub_only))
.or_else(|| self.packages.get_fn(hash_fn, pub_only));
if let Some(func) = func {
#[cfg(not(feature = "no_function"))]
@ -252,7 +253,11 @@ impl Engine {
// Getter function not found?
if let Some(prop) = extract_prop_from_getter(fn_name) {
return Err(Box::new(EvalAltResult::ErrorDotExpr(
format!("- property '{}' unknown or write-only", prop),
format!(
"Unknown property '{}' for {}, or it is write-only",
prop,
self.map_type_name(args[0].type_name())
),
Position::none(),
)));
}
@ -260,7 +265,11 @@ impl Engine {
// Setter function not found?
if let Some(prop) = extract_prop_from_setter(fn_name) {
return Err(Box::new(EvalAltResult::ErrorDotExpr(
format!("- property '{}' unknown or read-only", prop),
format!(
"Unknown property '{}' for {}, or it is read-only",
prop,
self.map_type_name(args[0].type_name())
),
Position::none(),
)));
}
@ -378,18 +387,18 @@ impl Engine {
}
// Has a system function an override?
fn has_override(&self, lib: &Module, hash_fn: u64, hash_script: u64) -> bool {
fn has_override(&self, lib: &Module, hash_fn: u64, hash_script: u64, pub_only: bool) -> bool {
// NOTE: We skip script functions for global_module and packages, and native functions for lib
// First check script-defined functions
lib.contains_fn(hash_script)
//|| lib.contains_fn(hash_fn)
// Then check registered functions
//|| self.global_module.contains_fn(hash_script)
|| self.global_module.contains_fn(hash_fn)
// Then check packages
//|| self.packages.contains_fn(hash_script)
|| self.packages.contains_fn(hash_fn)
lib.contains_fn(hash_script, pub_only)
//|| lib.contains_fn(hash_fn, pub_only)
// Then check registered functions
//|| self.global_module.contains_fn(hash_script, pub_only)
|| self.global_module.contains_fn(hash_fn, pub_only)
// Then check packages
//|| self.packages.contains_fn(hash_script, pub_only)
|| self.packages.contains_fn(hash_fn, pub_only)
}
/// Perform an actual function call, taking care of special functions
@ -410,6 +419,7 @@ impl Engine {
args: &mut FnCallArgs,
is_ref: bool,
is_method: bool,
pub_only: bool,
def_val: Option<bool>,
level: usize,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
@ -420,7 +430,9 @@ impl Engine {
match fn_name {
// type_of
KEYWORD_TYPE_OF if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => {
KEYWORD_TYPE_OF
if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1, pub_only) =>
{
Ok((
self.map_type_name(args[0].type_name()).to_string().into(),
false,
@ -428,7 +440,9 @@ impl Engine {
}
// Fn
KEYWORD_FN_PTR if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => {
KEYWORD_FN_PTR
if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1, pub_only) =>
{
Err(Box::new(EvalAltResult::ErrorRuntime(
"'Fn' should not be called in method style. Try Fn(...);".into(),
Position::none(),
@ -436,7 +450,9 @@ impl Engine {
}
// eval - reaching this point it must be a method-style call
KEYWORD_EVAL if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => {
KEYWORD_EVAL
if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1, pub_only) =>
{
Err(Box::new(EvalAltResult::ErrorRuntime(
"'eval' should not be called in method style. Try eval(...);".into(),
Position::none(),
@ -449,7 +465,7 @@ impl Engine {
let mut mods = Imports::new();
self.call_fn_raw(
&mut scope, &mut mods, state, lib, fn_name, hashes, args, is_ref, is_method,
def_val, level,
pub_only, def_val, level,
)
}
}
@ -499,28 +515,28 @@ impl Engine {
}
/// Call a dot method.
/// Position in `EvalAltResult` is `None` and must be set afterwards.
#[cfg(not(feature = "no_object"))]
pub(crate) fn make_method_call(
&self,
state: &mut State,
lib: &Module,
name: &str,
hash: u64,
target: &mut Target,
expr: &Expr,
idx_val: Dynamic,
def_val: Option<bool>,
native: bool,
pub_only: bool,
level: usize,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
let ((name, native, pos), _, hash, _, def_val) = match expr {
Expr::FnCall(x) => x.as_ref(),
_ => unreachable!(),
};
let is_ref = target.is_ref();
let is_value = target.is_value();
// Get a reference to the mutation target Dynamic
let obj = target.as_mut();
let mut idx = idx_val.cast::<StaticVec<Dynamic>>();
let mut _fn_name = name.as_ref();
let mut _fn_name = name;
let (result, updated) = if _fn_name == KEYWORD_FN_PTR_CALL && obj.is::<FnPtr>() {
// FnPtr call
@ -539,7 +555,7 @@ impl Engine {
// Map it to name(args) in function-call style
self.exec_fn_call(
state, lib, fn_name, *native, hash, args, false, false, *def_val, level,
state, lib, fn_name, native, hash, args, false, false, pub_only, def_val, level,
)
} else if _fn_name == KEYWORD_FN_PTR_CALL && idx.len() > 0 && idx[0].is::<FnPtr>() {
// FnPtr call on object
@ -558,7 +574,7 @@ impl Engine {
// Map it to name(args) in function-call style
self.exec_fn_call(
state, lib, &fn_name, *native, hash, args, is_ref, true, *def_val, level,
state, lib, &fn_name, native, hash, args, is_ref, true, pub_only, def_val, level,
)
} else if _fn_name == KEYWORD_FN_PTR_CURRY && obj.is::<FnPtr>() {
// Curry call
@ -579,7 +595,7 @@ impl Engine {
} else {
#[cfg(not(feature = "no_object"))]
let redirected;
let mut _hash = *hash;
let mut _hash = hash;
// Check if it is a map method call in OOP style
#[cfg(not(feature = "no_object"))]
@ -600,10 +616,9 @@ impl Engine {
let args = arg_values.as_mut();
self.exec_fn_call(
state, lib, _fn_name, *native, _hash, args, is_ref, true, *def_val, level,
state, lib, _fn_name, native, _hash, args, is_ref, true, pub_only, def_val, level,
)
}
.map_err(|err| err.new_position(*pos))?;
}?;
// Feed the changed temp value back
if updated && !is_ref && !is_value {
@ -628,13 +643,14 @@ impl Engine {
def_val: Option<bool>,
mut hash: u64,
native: bool,
pub_only: bool,
level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> {
// Handle Fn()
if name == KEYWORD_FN_PTR && args_expr.len() == 1 {
let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
if !self.has_override(lib, hash_fn, hash) {
if !self.has_override(lib, hash_fn, hash, pub_only) {
// Fn - only in function call style
let expr = args_expr.get(0).unwrap();
let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
@ -685,7 +701,7 @@ impl Engine {
if name == KEYWORD_EVAL && args_expr.len() == 1 {
let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
if !self.has_override(lib, hash_fn, hash) {
if !self.has_override(lib, hash_fn, hash, pub_only) {
// eval - only in function call style
let prev_len = scope.len();
let expr = args_expr.get(0).unwrap();
@ -710,7 +726,10 @@ impl Engine {
let mut curry: StaticVec<_> = Default::default();
let mut name = name;
if name == KEYWORD_FN_PTR_CALL && args_expr.len() >= 1 && !self.has_override(lib, 0, hash) {
if name == KEYWORD_FN_PTR_CALL
&& args_expr.len() >= 1
&& !self.has_override(lib, 0, hash, pub_only)
{
let expr = args_expr.get(0).unwrap();
let fn_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
@ -779,7 +798,7 @@ impl Engine {
let args = args.as_mut();
self.exec_fn_call(
state, lib, name, native, hash, args, is_ref, false, def_val, level,
state, lib, name, native, hash, args, is_ref, false, pub_only, def_val, level,
)
.map(|(v, _)| v)
}
@ -845,8 +864,8 @@ impl Engine {
// First search in script-defined functions (can override built-in)
let func = match module.get_qualified_fn(hash_script) {
Err(err) if matches!(*err, EvalAltResult::ErrorFunctionNotFound(_, _)) => {
// Then search in Rust functions
// Then search in Rust functions
None => {
self.inc_operations(state)?;
// Qualified Rust functions are indexed in two steps:
@ -865,7 +884,7 @@ impl Engine {
match func {
#[cfg(not(feature = "no_function"))]
Ok(f) if f.is_script() => {
Some(f) if f.is_script() => {
let args = args.as_mut();
let fn_def = f.get_fn_def();
let mut scope = Scope::new();
@ -874,32 +893,25 @@ impl Engine {
&mut scope, &mut mods, state, lib, &mut None, name, fn_def, args, level,
)
}
Ok(f) if f.is_plugin_fn() => f.get_plugin_fn().call(args.as_mut(), Position::none()),
Ok(f) => f.get_native_fn()(self, lib, args.as_mut()),
Err(err) => match *err {
EvalAltResult::ErrorFunctionNotFound(_, _) if def_val.is_some() => {
Ok(def_val.unwrap().into())
}
EvalAltResult::ErrorFunctionNotFound(_, pos) => {
Err(Box::new(EvalAltResult::ErrorFunctionNotFound(
format!(
"{}{} ({})",
modules,
name,
args.iter()
.map(|a| if a.is::<ImmutableString>() {
"&str | ImmutableString | String"
} else {
self.map_type_name((*a).type_name())
})
.collect::<Vec<_>>()
.join(", ")
),
pos,
)))
}
_ => Err(err),
},
Some(f) => f.get_native_fn()(self, lib, args.as_mut()),
Some(f) if f.is_plugin_fn() => f.get_plugin_fn().call(args.as_mut(), Position::none()),
None if def_val.is_some() => Ok(def_val.unwrap().into()),
None => Err(Box::new(EvalAltResult::ErrorFunctionNotFound(
format!(
"{}{} ({})",
modules,
name,
args.iter()
.map(|a| if a.is::<ImmutableString>() {
"&str | ImmutableString | String"
} else {
self.map_type_name((*a).type_name())
})
.collect::<Vec<_>>()
.join(", ")
),
Position::none(),
))),
}
}
}

View File

@ -1,17 +1,19 @@
//! Module defining interfaces to native-Rust functions.
use crate::any::Dynamic;
use crate::calc_fn_hash;
use crate::engine::Engine;
use crate::module::Module;
use crate::parser::FnAccess;
use crate::plugin::PluginFunction;
use crate::result::EvalAltResult;
use crate::token::{is_valid_identifier, Position};
use crate::utils::ImmutableString;
#[cfg(not(feature = "no_function"))]
use crate::{module::FuncReturn, parser::ScriptFnDef, scope::Scope, utils::StaticVec};
use crate::{module::FuncReturn, parser::ScriptFnDef, utils::StaticVec};
use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, string::String, vec::Vec};
use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, iter::empty, string::String, vec::Vec};
#[cfg(not(feature = "no_function"))]
use crate::stdlib::mem;
@ -90,9 +92,7 @@ impl FnPtr {
/// Call the function pointer with curried arguments (if any).
///
/// The function must be a script-defined function. It cannot be a Rust function.
///
/// To call a Rust function, just call it directly in Rust!
/// If this function is a script-defined function, it must not be marked private.
///
/// ## WARNING
///
@ -108,14 +108,39 @@ impl FnPtr {
this_ptr: Option<&mut Dynamic>,
mut arg_values: impl AsMut<[Dynamic]>,
) -> FuncReturn<Dynamic> {
let args = self
let mut args_data = self
.1
.iter()
.cloned()
.chain(arg_values.as_mut().iter_mut().map(|v| mem::take(v)))
.collect::<StaticVec<_>>();
engine.call_fn_dynamic(&mut Scope::new(), lib, self.0.as_str(), this_ptr, args)
let has_this = this_ptr.is_some();
let args_len = args_data.len();
let mut args = args_data.iter_mut().collect::<StaticVec<_>>();
if let Some(obj) = this_ptr {
args.insert(0, obj);
}
let fn_name = self.0.as_str();
let hash_script = calc_fn_hash(empty(), fn_name, args_len, empty());
engine
.exec_fn_call(
&mut Default::default(),
lib.as_ref(),
fn_name,
false,
hash_script,
args.as_mut(),
has_this,
has_this,
true,
None,
0,
)
.map(|(v, _)| v)
}
}
@ -272,6 +297,16 @@ impl CallableFunction {
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Script(_) => false,
}
}
/// Get the access mode.
pub fn access(&self) -> FnAccess {
match self {
CallableFunction::Plugin(_) => FnAccess::Public,
CallableFunction::Pure(_)
| CallableFunction::Method(_)
| CallableFunction::Iterator(_) => FnAccess::Public,
CallableFunction::Script(f) => f.access,
}
}
/// Get a reference to a native Rust function.
///
/// # Panics

View File

@ -355,10 +355,20 @@ impl Module {
///
/// let mut module = Module::new();
/// let hash = module.set_fn_0("calc", || Ok(42_i64));
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
pub fn contains_fn(&self, hash_fn: u64) -> bool {
self.functions.contains_key(&hash_fn)
pub fn contains_fn(&self, hash_fn: u64, public_only: bool) -> bool {
if public_only {
self.functions
.get(&hash_fn)
.map(|(_, access, _, _)| match access {
FnAccess::Public => true,
FnAccess::Private => false,
})
.unwrap_or(false)
} else {
self.functions.contains_key(&hash_fn)
}
}
/// Set a Rust function into the module, returning a hash key.
@ -422,7 +432,7 @@ impl Module {
/// let mut module = Module::new();
/// let hash = module.set_raw_fn("double_or_not",
/// // Pass parameter types via a slice with TypeId's
/// &[std::any::TypeId::of::<i64>(), std::any::TypeId::of::<bool>() ],
/// &[std::any::TypeId::of::<i64>(), std::any::TypeId::of::<bool>()],
/// // Fixed closure signature
/// |engine, lib, args| {
/// // 'args' is guaranteed to be the right length and of the correct types
@ -443,7 +453,7 @@ impl Module {
/// Ok(orig) // return Result<T, Box<EvalAltResult>>
/// });
///
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
pub fn set_raw_fn<T: Variant + Clone>(
&mut self,
@ -468,7 +478,7 @@ impl Module {
///
/// let mut module = Module::new();
/// let hash = module.set_fn_0("calc", || Ok(42_i64));
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
pub fn set_fn_0<T: Variant + Clone>(
&mut self,
@ -491,7 +501,7 @@ impl Module {
///
/// let mut module = Module::new();
/// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1));
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
pub fn set_fn_1<A: Variant + Clone, T: Variant + Clone>(
&mut self,
@ -516,7 +526,7 @@ impl Module {
///
/// let mut module = Module::new();
/// let hash = module.set_fn_1_mut("calc", |x: &mut i64| { *x += 1; Ok(*x) });
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
pub fn set_fn_1_mut<A: Variant + Clone, T: Variant + Clone>(
&mut self,
@ -541,7 +551,7 @@ impl Module {
///
/// let mut module = Module::new();
/// let hash = module.set_getter_fn("value", |x: &mut i64| { Ok(*x) });
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
#[cfg(not(feature = "no_object"))]
pub fn set_getter_fn<A: Variant + Clone, T: Variant + Clone>(
@ -565,7 +575,7 @@ impl Module {
/// let hash = module.set_fn_2("calc", |x: i64, y: ImmutableString| {
/// Ok(x + y.len() as i64)
/// });
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
pub fn set_fn_2<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
&mut self,
@ -596,7 +606,7 @@ impl Module {
/// let hash = module.set_fn_2_mut("calc", |x: &mut i64, y: ImmutableString| {
/// *x += y.len() as i64; Ok(*x)
/// });
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
pub fn set_fn_2_mut<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
&mut self,
@ -628,7 +638,7 @@ impl Module {
/// *x = y.len() as i64;
/// Ok(())
/// });
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
#[cfg(not(feature = "no_object"))]
pub fn set_setter_fn<A: Variant + Clone, B: Variant + Clone>(
@ -653,7 +663,7 @@ impl Module {
/// let hash = module.set_indexer_get_fn(|x: &mut i64, y: ImmutableString| {
/// Ok(*x + y.len() as i64)
/// });
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))]
@ -677,7 +687,7 @@ impl Module {
/// let hash = module.set_fn_3("calc", |x: i64, y: ImmutableString, z: i64| {
/// Ok(x + y.len() as i64 + z)
/// });
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
pub fn set_fn_3<
A: Variant + Clone,
@ -714,7 +724,7 @@ impl Module {
/// 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.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
pub fn set_fn_3_mut<
A: Variant + Clone,
@ -752,7 +762,7 @@ impl Module {
/// *x = y.len() as i64 + value;
/// Ok(())
/// });
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))]
@ -796,8 +806,8 @@ impl Module {
/// Ok(())
/// }
/// );
/// assert!(module.contains_fn(hash_get));
/// assert!(module.contains_fn(hash_set));
/// assert!(module.contains_fn(hash_get, true));
/// assert!(module.contains_fn(hash_set, true));
/// ```
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))]
@ -825,7 +835,7 @@ impl Module {
/// let hash = module.set_fn_4("calc", |x: i64, y: ImmutableString, z: i64, _w: ()| {
/// Ok(x + y.len() as i64 + z)
/// });
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
pub fn set_fn_4<
A: Variant + Clone,
@ -869,7 +879,7 @@ impl Module {
/// 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.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
pub fn set_fn_4_mut<
A: Variant + Clone,
@ -903,8 +913,14 @@ impl Module {
///
/// The `u64` hash is calculated by the function `crate::calc_fn_hash`.
/// It is also returned by the `set_fn_XXX` calls.
pub(crate) fn get_fn(&self, hash_fn: u64) -> Option<&Func> {
self.functions.get(&hash_fn).map(|(_, _, _, v)| v)
pub(crate) fn get_fn(&self, hash_fn: u64, public_only: bool) -> Option<&Func> {
self.functions
.get(&hash_fn)
.and_then(|(_, access, _, f)| match access {
_ if !public_only => Some(f),
FnAccess::Public => Some(f),
FnAccess::Private => None,
})
}
/// Get a modules-qualified function.
@ -912,16 +928,8 @@ impl Module {
///
/// The `u64` hash is calculated by the function `crate::calc_fn_hash` and must match
/// the hash calculated by `index_all_sub_modules`.
pub(crate) fn get_qualified_fn(
&self,
hash_qualified_fn: u64,
) -> Result<&Func, Box<EvalAltResult>> {
self.all_functions.get(&hash_qualified_fn).ok_or_else(|| {
Box::new(EvalAltResult::ErrorFunctionNotFound(
String::new(),
Position::none(),
))
})
pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&Func> {
self.all_functions.get(&hash_qualified_fn)
}
/// Merge another module into this module.

View File

@ -141,6 +141,7 @@ fn call_fn_with_constant_arguments(
arg_values.iter_mut().collect::<StaticVec<_>>().as_mut(),
false,
false,
true,
None,
0,
)
@ -184,6 +185,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
optimize_expr(expr, state),
optimize_stmt(x.1, state, true),
None,
x.3,
))),
},
// if expr { if_block } else { else_block }
@ -200,6 +202,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
Stmt::Noop(_) => None, // Noop -> no else block
stmt => Some(stmt),
},
x.3,
))),
},
// while expr { block }
@ -210,7 +213,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
Stmt::Noop(pos)
}
// while true { block } -> loop { block }
Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(x.1, state, false))),
Expr::True(_) => Stmt::Loop(Box::new((optimize_stmt(x.1, state, false), x.2))),
// while expr { block }
expr => match optimize_stmt(x.1, state, false) {
// while expr { break; } -> { expr; }
@ -225,11 +228,11 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
Stmt::Block(Box::new((statements, pos)))
}
// while expr { block }
stmt => Stmt::While(Box::new((optimize_expr(expr, state), stmt))),
stmt => Stmt::While(Box::new((optimize_expr(expr, state), stmt, x.2))),
},
},
// loop { block }
Stmt::Loop(block) => match optimize_stmt(*block, state, false) {
Stmt::Loop(x) => match optimize_stmt(x.0, state, false) {
// loop { break; } -> Noop
Stmt::Break(pos) => {
// Only a single break statement
@ -237,23 +240,26 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
Stmt::Noop(pos)
}
// loop { block }
stmt => Stmt::Loop(Box::new(stmt)),
stmt => Stmt::Loop(Box::new((stmt, x.1))),
},
// for id in expr { block }
Stmt::For(x) => Stmt::For(Box::new((
x.0,
optimize_expr(x.1, state),
optimize_stmt(x.2, state, false),
x.3,
))),
// let id = expr;
Stmt::Let(x) if x.1.is_some() => {
Stmt::Let(Box::new((x.0, Some(optimize_expr(x.1.unwrap(), state)))))
}
Stmt::Let(x) if x.1.is_some() => Stmt::Let(Box::new((
x.0,
Some(optimize_expr(x.1.unwrap(), state)),
x.2,
))),
// let id;
stmt @ Stmt::Let(_) => stmt,
// import expr as id;
#[cfg(not(feature = "no_module"))]
Stmt::Import(x) => Stmt::Import(Box::new((optimize_expr(x.0, state), x.1))),
Stmt::Import(x) => Stmt::Import(Box::new((optimize_expr(x.0, state), x.1, x.2))),
// { block }
Stmt::Block(x) => {
let orig_len = x.0.len(); // Original number of statements in the block, for change detection
@ -266,7 +272,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
.map(|stmt| match stmt {
// Add constant into the state
Stmt::Const(v) => {
let ((name, pos), expr) = *v;
let ((name, pos), expr, _) = *v;
state.push_constant(&name, expr);
state.set_dirty();
Stmt::Noop(pos) // No need to keep constants
@ -366,9 +372,11 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
// expr;
Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))),
// return expr;
Stmt::ReturnWithVal(x) if x.1.is_some() => {
Stmt::ReturnWithVal(Box::new((x.0, Some(optimize_expr(x.1.unwrap(), state)))))
}
Stmt::ReturnWithVal(x) if x.1.is_some() => Stmt::ReturnWithVal(Box::new((
x.0,
Some(optimize_expr(x.1.unwrap(), state)),
x.2,
))),
// All other statements - skip
stmt => stmt,
}
@ -411,7 +419,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
state.set_dirty();
let pos = m.1;
m.0.into_iter().find(|((name, _), _)| name.as_str() == prop.as_str())
.map(|(_, expr)| expr.set_position(pos))
.map(|(_, mut expr)| { expr.set_position(pos); expr })
.unwrap_or_else(|| Expr::Unit(pos))
}
// lhs.rhs
@ -428,7 +436,9 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
// Array literal where everything is pure - promote the indexed item.
// All other items can be thrown away.
state.set_dirty();
a.0.take(i.0 as usize).set_position(a.1)
let mut expr = a.0.take(i.0 as usize);
expr.set_position(a.1);
expr
}
// map[string]
(Expr::Map(m), Expr::StringConstant(s)) if m.0.iter().all(|(_, x)| x.is_pure()) => {
@ -437,7 +447,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
state.set_dirty();
let pos = m.1;
m.0.into_iter().find(|((name, _), _)| *name == s.0)
.map(|(_, expr)| expr.set_position(pos))
.map(|(_, mut expr)| { expr.set_position(pos); expr })
.unwrap_or_else(|| Expr::Unit(pos))
}
// string[int]
@ -624,7 +634,9 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
state.set_dirty();
// Replace constant with value
state.find_constant(&name).unwrap().clone().set_position(pos)
let mut expr = state.find_constant(&name).unwrap().clone();
expr.set_position(pos);
expr
}
// Custom syntax
@ -686,7 +698,7 @@ fn optimize(
match &stmt {
Stmt::Const(v) => {
// Load constants
let ((name, _), expr) = v.as_ref();
let ((name, _), expr, _) = v.as_ref();
state.push_constant(&name, expr.clone());
stmt // Keep it in the global scope
}

View File

@ -61,14 +61,15 @@ impl PackagesCollection {
self.0.insert(0, package);
}
/// Does the specified function hash key exist in the `PackagesCollection`?
pub fn contains_fn(&self, hash: u64) -> bool {
self.0.iter().any(|p| p.contains_fn(hash))
#[allow(dead_code)]
pub fn contains_fn(&self, hash: u64, public_only: bool) -> bool {
self.0.iter().any(|p| p.contains_fn(hash, public_only))
}
/// Get specified function via its hash key.
pub fn get_fn(&self, hash: u64) -> Option<&CallableFunction> {
pub fn get_fn(&self, hash: u64, public_only: bool) -> Option<&CallableFunction> {
self.0
.iter()
.map(|p| p.get_fn(hash))
.map(|p| p.get_fn(hash, public_only))
.find(|f| f.is_some())
.flatten()
}

View File

@ -26,7 +26,6 @@ use crate::stdlib::{
fmt, format,
hash::{Hash, Hasher},
iter::empty,
mem,
num::NonZeroUsize,
ops::Add,
string::{String, ToString},
@ -511,33 +510,38 @@ pub enum Stmt {
/// No-op.
Noop(Position),
/// if expr { stmt } else { stmt }
IfThenElse(Box<(Expr, Stmt, Option<Stmt>)>),
IfThenElse(Box<(Expr, Stmt, Option<Stmt>, Position)>),
/// while expr { stmt }
While(Box<(Expr, Stmt)>),
While(Box<(Expr, Stmt, Position)>),
/// loop { stmt }
Loop(Box<Stmt>),
Loop(Box<(Stmt, Position)>),
/// for id in expr { stmt }
For(Box<(String, Expr, Stmt)>),
For(Box<(String, Expr, Stmt, Position)>),
/// let id = expr
Let(Box<((String, Position), Option<Expr>)>),
Let(Box<((String, Position), Option<Expr>, Position)>),
/// const id = expr
Const(Box<((String, Position), Expr)>),
Const(Box<((String, Position), Expr, Position)>),
/// { stmt; ... }
Block(Box<(StaticVec<Stmt>, Position)>),
/// { stmt }
/// expr
Expr(Box<Expr>),
/// continue
Continue(Position),
/// break
Break(Position),
/// return/throw
ReturnWithVal(Box<((ReturnType, Position), Option<Expr>)>),
ReturnWithVal(Box<((ReturnType, Position), Option<Expr>, Position)>),
/// import expr as module
#[cfg(not(feature = "no_module"))]
Import(Box<(Expr, (String, Position))>),
Import(Box<(Expr, (String, Position), Position)>),
/// expr id as name, ...
#[cfg(not(feature = "no_module"))]
Export(Box<StaticVec<((String, Position), Option<(String, Position)>)>>),
Export(
Box<(
StaticVec<((String, Position), Option<(String, Position)>)>,
Position,
)>,
),
}
impl Default for Stmt {
@ -555,19 +559,44 @@ impl Stmt {
Stmt::Const(x) => (x.0).1,
Stmt::ReturnWithVal(x) => (x.0).1,
Stmt::Block(x) => x.1,
Stmt::IfThenElse(x) => x.0.position(),
Stmt::IfThenElse(x) => x.3,
Stmt::Expr(x) => x.position(),
Stmt::While(x) => x.1.position(),
Stmt::Loop(x) => x.position(),
Stmt::For(x) => x.2.position(),
Stmt::While(x) => x.2,
Stmt::Loop(x) => x.1,
Stmt::For(x) => x.3,
#[cfg(not(feature = "no_module"))]
Stmt::Import(x) => (x.1).1,
Stmt::Import(x) => x.2,
#[cfg(not(feature = "no_module"))]
Stmt::Export(x) => (x.get(0).0).1,
Stmt::Export(x) => x.1,
}
}
/// Override the `Position` of this statement.
pub fn set_position(&mut self, new_pos: Position) -> &mut Self {
match self {
Stmt::Noop(pos) | Stmt::Continue(pos) | Stmt::Break(pos) => *pos = new_pos,
Stmt::Let(x) => (x.0).1 = new_pos,
Stmt::Const(x) => (x.0).1 = new_pos,
Stmt::ReturnWithVal(x) => (x.0).1 = new_pos,
Stmt::Block(x) => x.1 = new_pos,
Stmt::IfThenElse(x) => x.3 = new_pos,
Stmt::Expr(x) => {
x.set_position(new_pos);
}
Stmt::While(x) => x.2 = new_pos,
Stmt::Loop(x) => x.1 = new_pos,
Stmt::For(x) => x.3 = new_pos,
#[cfg(not(feature = "no_module"))]
Stmt::Import(x) => x.2 = new_pos,
#[cfg(not(feature = "no_module"))]
Stmt::Export(x) => x.1 = new_pos,
}
self
}
/// Is this statement self-terminated (i.e. no need for a semicolon terminator)?
pub fn is_self_terminated(&self) -> bool {
match self {
@ -602,7 +631,7 @@ impl Stmt {
}
Stmt::IfThenElse(x) => x.1.is_pure(),
Stmt::While(x) => x.0.is_pure() && x.1.is_pure(),
Stmt::Loop(x) => x.is_pure(),
Stmt::Loop(x) => x.0.is_pure(),
Stmt::For(x) => x.1.is_pure() && x.2.is_pure(),
Stmt::Let(_) | Stmt::Const(_) => false,
Stmt::Block(x) => x.0.iter().all(Stmt::is_pure),
@ -832,11 +861,10 @@ 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));
pub fn set_position(&mut self, new_pos: Position) -> &mut Self {
match self {
Self::Expr(x) => {
x.set_position(new_pos);
}
#[cfg(not(feature = "no_float"))]
@ -2314,7 +2342,7 @@ fn ensure_not_statement_expr(input: &mut TokenStream, type_name: &str) -> Result
fn ensure_not_assignment(input: &mut TokenStream) -> Result<(), ParseError> {
match input.peek().unwrap() {
(Token::Equals, pos) => {
return Err(PERR::BadInput("Possibly a typo of '=='?".to_string()).into_err(*pos))
Err(PERR::BadInput("Possibly a typo of '=='?".to_string()).into_err(*pos))
}
(Token::PlusAssign, pos)
| (Token::MinusAssign, pos)
@ -2326,12 +2354,10 @@ fn ensure_not_assignment(input: &mut TokenStream) -> Result<(), ParseError> {
| (Token::PowerOfAssign, pos)
| (Token::AndAssign, pos)
| (Token::OrAssign, pos)
| (Token::XOrAssign, pos) => {
return Err(PERR::BadInput(
"Expecting a boolean expression, not an assignment".to_string(),
)
.into_err(*pos))
}
| (Token::XOrAssign, pos) => Err(PERR::BadInput(
"Expecting a boolean expression, not an assignment".to_string(),
)
.into_err(*pos)),
_ => Ok(()),
}
@ -2345,7 +2371,8 @@ fn parse_if(
mut settings: ParseSettings,
) -> Result<Stmt, ParseError> {
// if ...
settings.pos = eat_token(input, Token::If);
let token_pos = eat_token(input, Token::If);
settings.pos = token_pos;
#[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
@ -2369,7 +2396,9 @@ fn parse_if(
None
};
Ok(Stmt::IfThenElse(Box::new((guard, if_body, else_body))))
Ok(Stmt::IfThenElse(Box::new((
guard, if_body, else_body, token_pos,
))))
}
/// Parse a while loop.
@ -2380,7 +2409,8 @@ fn parse_while(
mut settings: ParseSettings,
) -> Result<Stmt, ParseError> {
// while ...
settings.pos = eat_token(input, Token::While);
let token_pos = eat_token(input, Token::While);
settings.pos = token_pos;
#[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
@ -2393,7 +2423,7 @@ fn parse_while(
settings.is_breakable = true;
let body = parse_block(input, state, lib, settings.level_up())?;
Ok(Stmt::While(Box::new((guard, body))))
Ok(Stmt::While(Box::new((guard, body, token_pos))))
}
/// Parse a loop statement.
@ -2404,7 +2434,8 @@ fn parse_loop(
mut settings: ParseSettings,
) -> Result<Stmt, ParseError> {
// loop ...
settings.pos = eat_token(input, Token::Loop);
let token_pos = eat_token(input, Token::Loop);
settings.pos = token_pos;
#[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
@ -2413,7 +2444,7 @@ fn parse_loop(
settings.is_breakable = true;
let body = parse_block(input, state, lib, settings.level_up())?;
Ok(Stmt::Loop(Box::new(body)))
Ok(Stmt::Loop(Box::new((body, token_pos))))
}
/// Parse a for loop.
@ -2424,7 +2455,8 @@ fn parse_for(
mut settings: ParseSettings,
) -> Result<Stmt, ParseError> {
// for ...
settings.pos = eat_token(input, Token::For);
let token_pos = eat_token(input, Token::For);
settings.pos = token_pos;
#[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
@ -2467,7 +2499,7 @@ fn parse_for(
state.stack.truncate(prev_stack_len);
Ok(Stmt::For(Box::new((name, expr, body))))
Ok(Stmt::For(Box::new((name, expr, body, token_pos))))
}
/// Parse a variable definition statement.
@ -2479,7 +2511,8 @@ fn parse_let(
mut settings: ParseSettings,
) -> Result<Stmt, ParseError> {
// let/const... (specified in `var_type`)
settings.pos = input.next().unwrap().1;
let token_pos = input.next().unwrap().1;
settings.pos = token_pos;
#[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
@ -2503,12 +2536,16 @@ fn parse_let(
// let name = expr
ScopeEntryType::Normal => {
state.stack.push((name.clone(), ScopeEntryType::Normal));
Ok(Stmt::Let(Box::new(((name, pos), Some(init_value)))))
Ok(Stmt::Let(Box::new((
(name, pos),
Some(init_value),
token_pos,
))))
}
// const name = { expr:constant }
ScopeEntryType::Constant if init_value.is_constant() => {
state.stack.push((name.clone(), ScopeEntryType::Constant));
Ok(Stmt::Const(Box::new(((name, pos), init_value))))
Ok(Stmt::Const(Box::new(((name, pos), init_value, token_pos))))
}
// const name = expr: error
ScopeEntryType::Constant => {
@ -2520,11 +2557,15 @@ fn parse_let(
match var_type {
ScopeEntryType::Normal => {
state.stack.push((name.clone(), ScopeEntryType::Normal));
Ok(Stmt::Let(Box::new(((name, pos), None))))
Ok(Stmt::Let(Box::new(((name, pos), None, token_pos))))
}
ScopeEntryType::Constant => {
state.stack.push((name.clone(), ScopeEntryType::Constant));
Ok(Stmt::Const(Box::new(((name, pos), Expr::Unit(pos)))))
Ok(Stmt::Const(Box::new((
(name, pos),
Expr::Unit(pos),
token_pos,
))))
}
}
}
@ -2539,7 +2580,8 @@ fn parse_import(
mut settings: ParseSettings,
) -> Result<Stmt, ParseError> {
// import ...
settings.pos = eat_token(input, Token::Import);
let token_pos = eat_token(input, Token::Import);
settings.pos = token_pos;
#[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
@ -2569,7 +2611,12 @@ fn parse_import(
};
state.modules.push(name.clone());
Ok(Stmt::Import(Box::new((expr, (name, settings.pos)))))
Ok(Stmt::Import(Box::new((
expr,
(name, settings.pos),
token_pos,
))))
}
/// Parse an export statement.
@ -2580,7 +2627,8 @@ fn parse_export(
_lib: &mut FunctionsLib,
mut settings: ParseSettings,
) -> Result<Stmt, ParseError> {
settings.pos = eat_token(input, Token::Export);
let token_pos = eat_token(input, Token::Export);
settings.pos = token_pos;
#[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(_state.max_expr_depth)?;
@ -2640,7 +2688,7 @@ fn parse_export(
})
.map_err(|(id2, pos)| PERR::DuplicatedExport(id2.to_string()).into_err(pos))?;
Ok(Stmt::Export(Box::new(exports)))
Ok(Stmt::Export(Box::new((exports, token_pos))))
}
/// Parse a statement block.
@ -2674,10 +2722,9 @@ fn parse_block(
// Parse statements inside the block
settings.is_global = false;
let stmt = if let Some(s) = parse_stmt(input, state, lib, settings.level_up())? {
s
} else {
continue;
let stmt = match parse_stmt(input, state, lib, settings.level_up())? {
Some(s) => s,
None => continue,
};
// See if it needs a terminating semicolon
@ -2828,22 +2875,32 @@ fn parse_stmt(
Token::Continue | Token::Break => Err(PERR::LoopBreak.into_err(settings.pos)),
Token::Return | Token::Throw => {
let return_type = match input.next().unwrap() {
(Token::Return, _) => ReturnType::Return,
(Token::Throw, _) => ReturnType::Exception,
_ => unreachable!(),
};
let (return_type, token_pos) = input
.next()
.map(|(token, pos)| {
(
match token {
Token::Return => ReturnType::Return,
Token::Throw => ReturnType::Exception,
_ => unreachable!(),
},
pos,
)
})
.unwrap();
match input.peek().unwrap() {
// `return`/`throw` at <EOF>
(Token::EOF, pos) => Ok(Some(Stmt::ReturnWithVal(Box::new((
(return_type, *pos),
None,
token_pos,
))))),
// `return;` or `throw;`
(Token::SemiColon, _) => Ok(Some(Stmt::ReturnWithVal(Box::new((
(return_type, settings.pos),
None,
token_pos,
))))),
// `return` or `throw` with expression
(_, _) => {
@ -2853,6 +2910,7 @@ fn parse_stmt(
Ok(Some(Stmt::ReturnWithVal(Box::new((
(return_type, pos),
Some(expr),
token_pos,
)))))
}
}
@ -3137,10 +3195,9 @@ impl Engine {
pos: Position::none(),
};
let stmt = if let Some(s) = parse_stmt(input, &mut state, &mut functions, settings)? {
s
} else {
continue;
let stmt = match parse_stmt(input, &mut state, &mut functions, settings)? {
Some(s) => s,
None => continue,
};
let need_semicolon = !stmt.is_self_terminated();

View File

@ -182,7 +182,7 @@ impl fmt::Display for EvalAltResult {
| Self::ErrorVariableNotFound(s, _)
| Self::ErrorModuleNotFound(s, _) => write!(f, "{}: '{}'", desc, s)?,
Self::ErrorDotExpr(s, _) if !s.is_empty() => write!(f, "{} {}", desc, s)?,
Self::ErrorDotExpr(s, _) if !s.is_empty() => write!(f, "{}", s)?,
Self::ErrorIndexingType(_, _)
| Self::ErrorNumericIndexExpr(_)

View File

@ -495,8 +495,8 @@ impl Token {
"===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "public"
| "new" | "use" | "module" | "package" | "var" | "static" | "with" | "do" | "each"
| "then" | "goto" | "exit" | "switch" | "match" | "case" | "try" | "catch"
| "default" | "void" | "null" | "nil" | "spawn" | "go" | "async" | "await"
| "yield" => Reserved(syntax.into()),
| "default" | "void" | "null" | "nil" | "spawn" | "go" | "shared" | "sync"
| "async" | "await" | "yield" => Reserved(syntax.into()),
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS => Reserved(syntax.into()),

View File

@ -1,6 +1,7 @@
#![cfg(not(feature = "no_function"))]
use rhai::{
Dynamic, Engine, EvalAltResult, FnPtr, Func, Module, ParseError, ParseErrorType, Scope, INT,
Dynamic, Engine, EvalAltResult, FnPtr, Func, Module, ParseError, ParseErrorType, RegisterFn,
Scope, INT,
};
use std::any::TypeId;
@ -119,21 +120,23 @@ fn test_anonymous_fn() -> Result<(), Box<EvalAltResult>> {
fn test_fn_ptr_raw() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
engine.register_raw_fn(
"bar",
&[
TypeId::of::<INT>(),
TypeId::of::<FnPtr>(),
TypeId::of::<INT>(),
],
move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
let fp = std::mem::take(args[1]).cast::<FnPtr>();
let value = args[2].clone();
let this_ptr = args.get_mut(0).unwrap();
engine
.register_fn("mul", |x: &mut INT, y: INT| *x *= y)
.register_raw_fn(
"bar",
&[
TypeId::of::<INT>(),
TypeId::of::<FnPtr>(),
TypeId::of::<INT>(),
],
move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
let fp = std::mem::take(args[1]).cast::<FnPtr>();
let value = args[2].clone();
let this_ptr = args.get_mut(0).unwrap();
fp.call_dynamic(engine, lib, Some(this_ptr), [value])
},
);
fp.call_dynamic(engine, lib, Some(this_ptr), [value])
},
);
assert_eq!(
engine.eval::<INT>(
@ -148,6 +151,30 @@ fn test_fn_ptr_raw() -> Result<(), Box<EvalAltResult>> {
42
);
assert!(matches!(
*engine.eval::<INT>(
r#"
private fn foo(x) { this += x; }
let x = 41;
x.bar(Fn("foo"), 1);
x
"#
).expect_err("should error"),
EvalAltResult::ErrorFunctionNotFound(x, _) if x.starts_with("foo (")
));
assert_eq!(
engine.eval::<INT>(
r#"
let x = 21;
x.bar(Fn("mul"), 2);
x
"#
)?,
42
);
Ok(())
}

View File

@ -35,7 +35,7 @@ fn test_module_sub_module() -> Result<(), Box<EvalAltResult>> {
let m2 = m.get_sub_module("universe").unwrap();
assert!(m2.contains_var("answer"));
assert!(m2.contains_fn(hash_inc));
assert!(m2.contains_fn(hash_inc, false));
assert_eq!(m2.get_var_value::<INT>("answer").unwrap(), 41);

View File

@ -1,11 +1,14 @@
#![cfg(not(feature = "no_std"))]
#![cfg(not(target_arch = "wasm32"))]
use rhai::{Engine, EvalAltResult, INT};
use rhai::{Engine, EvalAltResult};
#[cfg(not(feature = "no_float"))]
use rhai::FLOAT;
#[cfg(feature = "no_float")]
use rhai::INT;
#[test]
fn test_timestamp() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();