Merge branch 'master' into plugins
This commit is contained in:
commit
a7f564fe37
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rhai"
|
name = "rhai"
|
||||||
version = "0.17.0"
|
version = "0.18.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
|
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
|
||||||
description = "Embedded scripting for Rust"
|
description = "Embedded scripting for Rust"
|
||||||
@ -73,9 +73,3 @@ optional = true
|
|||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
instant= { version = "0.1.4", features = ["wasm-bindgen"] } # WASM implementation of std::time::Instant
|
instant= { version = "0.1.4", features = ["wasm-bindgen"] } # WASM implementation of std::time::Instant
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
|
||||||
features = ["serde"]
|
|
||||||
|
|
||||||
[package.metadata.playground]
|
|
||||||
features = ["serde"]
|
|
||||||
|
24
README.md
24
README.md
@ -18,8 +18,8 @@ Supported targets and builds
|
|||||||
* WebAssembly (WASM)
|
* WebAssembly (WASM)
|
||||||
* `no-std`
|
* `no-std`
|
||||||
|
|
||||||
Features
|
Standard features
|
||||||
--------
|
-----------------
|
||||||
|
|
||||||
* Easy-to-use language similar to JavaScript+Rust with dynamic typing.
|
* Easy-to-use language similar to JavaScript+Rust with dynamic typing.
|
||||||
* Tight integration with native Rust [functions](https://schungx.github.io/rhai/rust/functions.html) and [types]([#custom-types-and-methods](https://schungx.github.io/rhai/rust/custom.html)), including [getters/setters](https://schungx.github.io/rhai/rust/getters-setters.html), [methods](https://schungx.github.io/rhai/rust/custom.html) and [indexers](https://schungx.github.io/rhai/rust/indexers.html).
|
* Tight integration with native Rust [functions](https://schungx.github.io/rhai/rust/functions.html) and [types]([#custom-types-and-methods](https://schungx.github.io/rhai/rust/custom.html)), including [getters/setters](https://schungx.github.io/rhai/rust/getters-setters.html), [methods](https://schungx.github.io/rhai/rust/custom.html) and [indexers](https://schungx.github.io/rhai/rust/indexers.html).
|
||||||
@ -30,20 +30,30 @@ Features
|
|||||||
* Relatively little `unsafe` code (yes there are some for performance reasons, and most `unsafe` code is limited to
|
* 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_"`).
|
one single source file, all with names starting with `"unsafe_"`).
|
||||||
* Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature).
|
* Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature).
|
||||||
* Sand-boxed - the scripting engine, if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`).
|
|
||||||
* Rugged - protected against malicious attacks (such as [stack-overflow](https://schungx.github.io/rhai/safety/max-call-stack.html), [over-sized data](https://schungx.github.io/rhai/safety/max-string-size.html), and [runaway scripts](https://schungx.github.io/rhai/safety/max-operations.html) etc.) that may come from untrusted third-party user-land scripts.
|
|
||||||
* Track script evaluation [progress](https://schungx.github.io/rhai/safety/progress.html) and manually terminate a script run.
|
|
||||||
* [Function overloading](https://schungx.github.io/rhai/language/overload.html).
|
* [Function overloading](https://schungx.github.io/rhai/language/overload.html).
|
||||||
* [Operator overloading](https://schungx.github.io/rhai/rust/operators.html).
|
* [Operator overloading](https://schungx.github.io/rhai/rust/operators.html).
|
||||||
* Support for use as a [DSL](https://schungx.github.io/rhai/engine/dsl.html) - [disabling keywords/operators](https://schungx.github.io/rhai/engine/disable.html), [custom operators](https://schungx.github.io/rhai/engine/custom-op.html) and extending the language with [custom syntax](https://schungx.github.io/rhai/engine/custom-syntax.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).
|
||||||
* Some support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.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).
|
* 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).
|
* Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature).
|
||||||
* Surgically disable keywords and operators to restrict the language.
|
|
||||||
* Scripts are [optimized](https://schungx.github.io/rhai/engine/optimize.html) (useful for template-based machine-generated scripts) for repeated evaluations.
|
* Scripts are [optimized](https://schungx.github.io/rhai/engine/optimize.html) (useful for template-based machine-generated scripts) for repeated evaluations.
|
||||||
* Support for [minimal builds](https://schungx.github.io/rhai/start/builds/minimal.html) by excluding unneeded language [features](https://schungx.github.io/rhai/start/features.html).
|
* Support for [minimal builds](https://schungx.github.io/rhai/start/builds/minimal.html) by excluding unneeded language [features](https://schungx.github.io/rhai/start/features.html).
|
||||||
|
|
||||||
|
Protection against attacks
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
* Sand-boxed - the scripting engine, if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`).
|
||||||
|
* Rugged - protected against malicious attacks (such as [stack-overflow](https://schungx.github.io/rhai/safety/max-call-stack.html), [over-sized data](https://schungx.github.io/rhai/safety/max-string-size.html), and [runaway scripts](https://schungx.github.io/rhai/safety/max-operations.html) etc.) that may come from untrusted third-party user-land scripts.
|
||||||
|
* Track script evaluation [progress](https://schungx.github.io/rhai/safety/progress.html) and manually terminate a script run.
|
||||||
|
|
||||||
|
For those who actually want their own language
|
||||||
|
---------------------------------------------
|
||||||
|
|
||||||
|
* Use as a [DSL](https://schungx.github.io/rhai/engine/dsl.html).
|
||||||
|
* Restrict the language by surgically [disabling keywords and operators](https://schungx.github.io/rhai/engine/disable.html).
|
||||||
|
* Define [custom operators](https://schungx.github.io/rhai/engine/custom-op.html).
|
||||||
|
* Extend the language with [custom syntax](https://schungx.github.io/rhai/engine/custom-syntax.html).
|
||||||
|
|
||||||
Documentation
|
Documentation
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
20
RELEASES.md
20
RELEASES.md
@ -1,15 +1,27 @@
|
|||||||
Rhai Release Notes
|
Rhai Release Notes
|
||||||
==================
|
==================
|
||||||
|
|
||||||
|
Version 0.18.0
|
||||||
|
==============
|
||||||
|
|
||||||
|
New features
|
||||||
|
------------
|
||||||
|
|
||||||
|
* `call` can now be called function-call style for function pointers - this is to handle builds with `no_object`.
|
||||||
|
* Disallow many keywords as variables, such as `print`, `eval`, `call`, `this` etc.
|
||||||
|
* `x.call(f, ...)` allows binding `x` to `this` for the function referenced by the function pointer `f`.
|
||||||
|
|
||||||
|
|
||||||
Version 0.17.0
|
Version 0.17.0
|
||||||
==============
|
==============
|
||||||
|
|
||||||
This version adds:
|
This version adds:
|
||||||
|
|
||||||
* [`serde`](https://crates.io/crates/serde) support for working with `Dynamic` values (particularly _object maps_).
|
* [`serde`](https://crates.io/crates/serde) support for working with `Dynamic` values (particularly _object maps_).
|
||||||
* Ability to surgically disable keywords and/or operators in the language.
|
|
||||||
* Ability to define custom operators (which must be valid identifiers).
|
|
||||||
* Low-level API to register functions.
|
* Low-level API to register functions.
|
||||||
|
* Surgically disable keywords and/or operators in the language.
|
||||||
|
* Define custom operators.
|
||||||
|
* Extend the language via custom syntax.
|
||||||
|
|
||||||
Bug fixes
|
Bug fixes
|
||||||
---------
|
---------
|
||||||
@ -22,7 +34,6 @@ Breaking changes
|
|||||||
* `EvalAltResult::ErrorMismatchOutputType` has an extra argument containing the name of the requested type.
|
* `EvalAltResult::ErrorMismatchOutputType` has an extra argument containing the name of the requested type.
|
||||||
* `Engine::call_fn_dynamic` take an extra argument, allowing a `Dynamic` value to be bound to the `this` pointer.
|
* `Engine::call_fn_dynamic` take an extra argument, allowing a `Dynamic` value to be bound to the `this` pointer.
|
||||||
* Precedence of the `%` (modulo) operator is lowered to below `<<` ad `>>`. This is to handle the case of `x << 3 % 10`.
|
* Precedence of the `%` (modulo) operator is lowered to below `<<` ad `>>`. This is to handle the case of `x << 3 % 10`.
|
||||||
* Many configuration/setting API's now returns `&mut Self` so that the calls can be chained. This should not affect most code.
|
|
||||||
|
|
||||||
New features
|
New features
|
||||||
------------
|
------------
|
||||||
@ -31,6 +42,7 @@ New features
|
|||||||
This is particularly useful when converting a Rust `struct` to a `Dynamic` _object map_ and back.
|
This is particularly useful when converting a Rust `struct` to a `Dynamic` _object map_ and back.
|
||||||
* `Engine::disable_symbol` to surgically disable keywords and/or operators.
|
* `Engine::disable_symbol` to surgically disable keywords and/or operators.
|
||||||
* `Engine::register_custom_operator` to define a custom operator.
|
* `Engine::register_custom_operator` to define a custom operator.
|
||||||
|
* `Engine::register_custom_syntax` to define a custom syntax.
|
||||||
* New low-level API `Engine::register_raw_fn` and `Engine::register_raw_fn_XXX`.
|
* New low-level API `Engine::register_raw_fn` and `Engine::register_raw_fn_XXX`.
|
||||||
* New low-level API `Module::set_raw_fn` mirroring `Engine::register_raw_fn`.
|
* New low-level API `Module::set_raw_fn` mirroring `Engine::register_raw_fn`.
|
||||||
* `AST::clone_functions_only`, `AST::clone_functions_only_filtered` and `AST::clone_statements_only` to clone only part of an `AST`.
|
* `AST::clone_functions_only`, `AST::clone_functions_only_filtered` and `AST::clone_statements_only` to clone only part of an `AST`.
|
||||||
@ -38,6 +50,8 @@ New features
|
|||||||
* `FnPtr` is exposed as the function pointer type.
|
* `FnPtr` is exposed as the function pointer type.
|
||||||
* `rhai::module_resolvers::ModuleResolversCollection` added to try a list of module resolvers.
|
* `rhai::module_resolvers::ModuleResolversCollection` added to try a list of module resolvers.
|
||||||
* It is now possible to mutate the first argument of a module-qualified function call when the argument is a simple variable (but not a module constant).
|
* It is now possible to mutate the first argument of a module-qualified function call when the argument is a simple variable (but not a module constant).
|
||||||
|
* Many configuration/setting API's now returns `&mut Self` so that the calls can be chained.
|
||||||
|
* `String` parameters in functions are supported (but inefficiently).
|
||||||
|
|
||||||
|
|
||||||
Version 0.16.1
|
Version 0.16.1
|
||||||
|
@ -5,6 +5,12 @@ Advanced Topics
|
|||||||
|
|
||||||
This section covers advanced features such as:
|
This section covers advanced features such as:
|
||||||
|
|
||||||
* [Script optimization]
|
* Simulated [Object Oriented Programming][OOP].
|
||||||
|
|
||||||
* The dreaded (or beloved for those with twisted tastes) [`eval`] statement
|
* [`serde`] integration.
|
||||||
|
|
||||||
|
* [Script optimization].
|
||||||
|
|
||||||
|
* [Domain-Specific Languages][DSL].
|
||||||
|
|
||||||
|
* The dreaded (or beloved for those with twisted tastes) [`eval`] statement.
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "0.17.0",
|
"version": "0.18.0",
|
||||||
"rootUrl": "",
|
"rootUrl": "",
|
||||||
"rootUrlX": "/rhai",
|
"rootUrlX": "/rhai",
|
||||||
"rootUrlXX": "/rhai/vnext"
|
"rootUrlXX": "/rhai/vnext"
|
||||||
|
@ -91,5 +91,5 @@ let result = engine.call_fn_dynamic(
|
|||||||
[ 41_i64.into() ]
|
[ 41_i64.into() ]
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
assert_eq!(value.as_int().unwrap(), 42);
|
assert_eq!(value.as_int()?, 42);
|
||||||
```
|
```
|
||||||
|
@ -21,7 +21,7 @@ let mut engine = Engine::new();
|
|||||||
// (i.e. between +|- and *|/)
|
// (i.e. between +|- and *|/)
|
||||||
// Also register the implementation of the customer operator as a function
|
// Also register the implementation of the customer operator as a function
|
||||||
engine
|
engine
|
||||||
.register_custom_operator("foo", 160).unwrap()
|
.register_custom_operator("foo", 160)?
|
||||||
.register_fn("foo", |x: i64, y: i64| (x * y) - (x + y));
|
.register_fn("foo", |x: i64, y: i64| (x * y) - (x + y));
|
||||||
|
|
||||||
// The custom operator can be used in expressions
|
// The custom operator can be used in expressions
|
||||||
@ -72,7 +72,7 @@ _Unary_ custom operators are not supported.
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
engine
|
engine
|
||||||
.register_custom_operator("foo", 160).unwrap()
|
.register_custom_operator("foo", 160)?
|
||||||
.register_fn("foo", |x: i64| x * x);
|
.register_fn("foo", |x: i64| x * x);
|
||||||
|
|
||||||
engine.eval::<i64>("1 + 2 * 3 foo 4 - 5 / 6")?; // error: function 'foo (i64, i64)' not found
|
engine.eval::<i64>("1 + 2 * 3 foo 4 - 5 / 6")?; // error: function 'foo (i64, i64)' not found
|
||||||
|
@ -66,7 +66,7 @@ item.is::<i64>() == true; // 'is' returns whether a 'Dynam
|
|||||||
let value = item.cast::<i64>(); // if the element is 'i64', this succeeds; otherwise it panics
|
let value = item.cast::<i64>(); // if the element is 'i64', this succeeds; otherwise it panics
|
||||||
let value: i64 = item.cast(); // type can also be inferred
|
let value: i64 = item.cast(); // type can also be inferred
|
||||||
|
|
||||||
let value = item.try_cast::<i64>().unwrap(); // 'try_cast' does not panic when the cast fails, but returns 'None'
|
let value = item.try_cast::<i64>()?; // 'try_cast' does not panic when the cast fails, but returns 'None'
|
||||||
```
|
```
|
||||||
|
|
||||||
Type Name
|
Type Name
|
||||||
|
@ -6,7 +6,7 @@ Function Pointers
|
|||||||
It is possible to store a _function pointer_ in a variable just like a normal value.
|
It is possible to store a _function pointer_ in a variable just like a normal value.
|
||||||
In fact, internally a function pointer simply stores the _name_ of the function as a string.
|
In fact, internally a function pointer simply stores the _name_ of the function as a string.
|
||||||
|
|
||||||
Call a function pointer using the `call` method, which needs to be called in method-call style.
|
Call a function pointer using the `call` method.
|
||||||
|
|
||||||
|
|
||||||
Built-in methods
|
Built-in methods
|
||||||
@ -40,7 +40,7 @@ func.call(1) == 42; // call a function pointer with the 'call' method
|
|||||||
|
|
||||||
foo(1) == 42; // <- the above de-sugars to this
|
foo(1) == 42; // <- the above de-sugars to this
|
||||||
|
|
||||||
call(func, 1); //<- error: 'call (Fn, i64)' is not a registered function
|
call(func, 1); // normal function call style also works for 'call'
|
||||||
|
|
||||||
let len = Fn("len"); // 'Fn' also works with registered native Rust functions
|
let len = Fn("len"); // 'Fn' also works with registered native Rust functions
|
||||||
|
|
||||||
@ -66,20 +66,18 @@ Because of their dynamic nature, function pointers cannot refer to functions in
|
|||||||
See [function namespaces] for more details.
|
See [function namespaces] for more details.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
import "foo" as f; // assume there is 'f::do_something()'
|
import "foo" as f; // assume there is 'f::do_work()'
|
||||||
|
|
||||||
f::do_something(); // works!
|
f::do_work(); // works!
|
||||||
|
|
||||||
let p = Fn("f::do_something");
|
let p = Fn("f::do_work"); // error: invalid function name
|
||||||
|
|
||||||
p.call(); // error: function not found - 'f::do_something'
|
fn do_work_now() { // call it from a local function
|
||||||
|
|
||||||
fn do_something_now() { // call it from a local function
|
|
||||||
import "foo" as f;
|
import "foo" as f;
|
||||||
f::do_something();
|
f::do_work();
|
||||||
}
|
}
|
||||||
|
|
||||||
let p = Fn("do_something_now");
|
let p = Fn("do_work_now");
|
||||||
|
|
||||||
p.call(); // works!
|
p.call(); // works!
|
||||||
```
|
```
|
||||||
@ -134,3 +132,35 @@ let func = sign(x) + 1;
|
|||||||
// Dynamic dispatch
|
// Dynamic dispatch
|
||||||
map[func].call(42);
|
map[func].call(42);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Binding the `this` Pointer
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
When `call` is called as a _method_ but not on a `FnPtr` value, it is possible to dynamically dispatch
|
||||||
|
to a function call while binding the object in the method call to the `this` pointer of the function.
|
||||||
|
|
||||||
|
To achieve this, pass the `FnPtr` value as the _first_ argument to `call`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn add(x) { this += x; } // define function which uses 'this'
|
||||||
|
|
||||||
|
let func = Fn("add"); // function pointer to 'add'
|
||||||
|
|
||||||
|
func.call(1); // error: 'this' pointer is not bound
|
||||||
|
|
||||||
|
let x = 41;
|
||||||
|
|
||||||
|
func.call(x, 1); // error: function 'add (i64, i64)' not found
|
||||||
|
|
||||||
|
call(func, x, 1); // error: function 'add (i64, i64)' not found
|
||||||
|
|
||||||
|
x.call(func, 1); // 'this' is bound to 'x', dispatched to 'func'
|
||||||
|
|
||||||
|
x == 42;
|
||||||
|
```
|
||||||
|
|
||||||
|
Beware that this only works for _method-call_ style. Normal function-call style cannot bind
|
||||||
|
the `this` pointer (for syntactic reasons).
|
||||||
|
|
||||||
|
Therefore, obviously, binding the `this` pointer is unsupported under [`no_function`].
|
||||||
|
@ -3,6 +3,10 @@ Call Method as Function
|
|||||||
|
|
||||||
{{#include ../links.md}}
|
{{#include ../links.md}}
|
||||||
|
|
||||||
|
|
||||||
|
First `&mut` Reference Parameter
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
Property [getters/setters] and [methods][custom types] in a Rust custom type registered with the [`Engine`] can be called
|
Property [getters/setters] and [methods][custom types] in a Rust custom type registered with the [`Engine`] can be called
|
||||||
just like a regular function. In fact, like Rust, property getters/setters and object methods
|
just like a regular function. In fact, like Rust, property getters/setters and object methods
|
||||||
are registered as regular [functions] in Rhai that take a first `&mut` parameter.
|
are registered as regular [functions] in Rhai that take a first `&mut` parameter.
|
||||||
@ -31,3 +35,23 @@ update(array[0]); // <- 'array[0]' is an expression returning a calculated val
|
|||||||
|
|
||||||
array[0].update(); // <- call in method-call style will update 'a'
|
array[0].update(); // <- call in method-call style will update 'a'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Encouraged Usage
|
||||||
|
----------------
|
||||||
|
|
||||||
|
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 primary types that are cheap to clone, including `ImmutableString`, this is not necessary.
|
||||||
|
|
||||||
|
|
||||||
|
Avoid `&mut ImmutableString`
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
`ImmutableString`, Rhai internal [string] type, is an exception.
|
||||||
|
|
||||||
|
`ImmutableString` is cheap to clone, but expensive to take a mutable reference (because the underlying
|
||||||
|
string must be cloned to make a private copy).
|
||||||
|
|
||||||
|
Therefore, avoid using `&mut ImmutableString` unless the intention is to mutate it.
|
||||||
|
@ -6,20 +6,25 @@ Strings and Characters
|
|||||||
String in Rhai contain any text sequence of valid Unicode characters.
|
String in Rhai contain any text sequence of valid Unicode characters.
|
||||||
Internally strings are stored in UTF-8 encoding.
|
Internally strings are stored in UTF-8 encoding.
|
||||||
|
|
||||||
Strings can be built up from other strings and types via the `+` operator (provided by the [`MoreStringPackage`][packages]
|
Strings can be built up from other strings and types via the `+` operator
|
||||||
but excluded if using a [raw `Engine`]). This is particularly useful when printing output.
|
(provided by the [`MoreStringPackage`][packages] but excluded if using a [raw `Engine`]).
|
||||||
|
This is particularly useful when printing output.
|
||||||
|
|
||||||
[`type_of()`] a string returns `"string"`.
|
[`type_of()`] a string returns `"string"`.
|
||||||
|
|
||||||
The maximum allowed length of a string can be controlled via `Engine::set_max_string_size`
|
The maximum allowed length of a string can be controlled via `Engine::set_max_string_size`
|
||||||
(see [maximum length of strings]).
|
(see [maximum length of strings]).
|
||||||
|
|
||||||
|
|
||||||
The `ImmutableString` Type
|
The `ImmutableString` Type
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
All strings in Rhai are implemented as `ImmutableString` (see [standard types]).
|
All strings in Rhai are implemented as `ImmutableString` (see [standard types]).
|
||||||
|
|
||||||
`ImmutableString` should be used in place of the standard Rust type `String` when registering functions.
|
`ImmutableString` should be used in place of the standard Rust type `String` when registering functions
|
||||||
|
because using `String` is very inefficient (the `String` must always be cloned).
|
||||||
|
|
||||||
|
A alternative is to use `&str` which maps straight to `ImmutableString`.
|
||||||
|
|
||||||
|
|
||||||
String and Character Literals
|
String and Character Literals
|
||||||
@ -59,13 +64,13 @@ Unicode characters.
|
|||||||
|
|
||||||
Individual characters within a Rhai string can also be replaced just as if the string is an array of Unicode characters.
|
Individual characters within a Rhai string can also be replaced just as if the string is an array of Unicode characters.
|
||||||
|
|
||||||
In Rhai, there is also no separate concepts of `String` and `&str` as in Rust.
|
In Rhai, there are also no separate concepts of `String` and `&str` as in Rust.
|
||||||
|
|
||||||
|
|
||||||
Immutable Strings
|
Immutable Strings
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
Rhai strings are _immutable_ and can be shared.
|
Rhai use _immutable_ strings (type `ImmutableString`) and can be shared.
|
||||||
|
|
||||||
Modifying a Rhai string actually causes it first to be cloned, and then the modification made to the copy.
|
Modifying a Rhai string actually causes it first to be cloned, and then the modification made to the copy.
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@ Custom Type Getters and Setters
|
|||||||
|
|
||||||
A custom type can also expose members by registering `get` and/or `set` functions.
|
A custom type can also expose members by registering `get` and/or `set` functions.
|
||||||
|
|
||||||
|
Getters and setters each take a `&mut` reference to the first parameter.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct TestStruct {
|
struct TestStruct {
|
||||||
|
@ -7,6 +7,8 @@ A custom type can also expose an _indexer_ by registering an indexer function.
|
|||||||
|
|
||||||
A custom type with an indexer function defined can use the bracket '`[]`' notation to get a property value.
|
A custom type with an indexer function defined can use the bracket '`[]`' notation to get a property value.
|
||||||
|
|
||||||
|
Like getters and setters, indexers take a `&mut` reference to the first parameter.
|
||||||
|
|
||||||
Indexers are disabled when the [`no_index`] feature is used.
|
Indexers are disabled when the [`no_index`] feature is used.
|
||||||
|
|
||||||
For efficiency reasons, indexers **cannot** be used to overload (i.e. override) built-in indexing operations for
|
For efficiency reasons, indexers **cannot** be used to overload (i.e. override) built-in indexing operations for
|
||||||
@ -33,12 +35,10 @@ impl TestStruct {
|
|||||||
|
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
engine.register_type::<TestStruct>();
|
|
||||||
|
|
||||||
engine.register_fn("new_ts", TestStruct::new);
|
|
||||||
|
|
||||||
// Shorthand: engine.register_indexer_get_set(TestStruct::get_field, TestStruct::set_field);
|
|
||||||
engine
|
engine
|
||||||
|
.register_type::<TestStruct>()
|
||||||
|
.register_fn("new_ts", TestStruct::new)
|
||||||
|
// Shorthand: .register_indexer_get_set(TestStruct::get_field, TestStruct::set_field);
|
||||||
.register_indexer_get(TestStruct::get_field)
|
.register_indexer_get(TestStruct::get_field)
|
||||||
.register_indexer_set(TestStruct::set_field);
|
.register_indexer_set(TestStruct::set_field);
|
||||||
|
|
||||||
|
@ -3,11 +3,19 @@
|
|||||||
|
|
||||||
{{#include ../links.md}}
|
{{#include ../links.md}}
|
||||||
|
|
||||||
Rust functions accepting parameters of `String` should use `&str` instead because it maps directly to [`ImmutableString`]
|
|
||||||
which is the type that Rhai uses to represent [strings] internally.
|
`&str` Maps to `ImmutableString`
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
Rust functions accepting parameters of `String` should use `&str` instead because it maps directly to
|
||||||
|
[`ImmutableString`][string] which is the type that Rhai uses to represent [strings] internally.
|
||||||
|
|
||||||
|
The parameter type `String` is discouraged because it involves converting an [`ImmutableString`] into a `String`.
|
||||||
|
Using `ImmutableString` or `&str` is much more efficient.
|
||||||
|
A common mistake made by novice Rhai users is to register functions with `String` parameters.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn get_len1(s: String) -> i64 { s.len() as i64 } // <- Rhai will not find this function
|
fn get_len1(s: String) -> i64 { s.len() as i64 } // <- Rhai finds this function, but very inefficient
|
||||||
fn get_len2(s: &str) -> i64 { s.len() as i64 } // <- Rhai finds this function fine
|
fn get_len2(s: &str) -> i64 { s.len() as i64 } // <- Rhai finds this function fine
|
||||||
fn get_len3(s: ImmutableString) -> i64 { s.len() as i64 } // <- the above is equivalent to this
|
fn get_len3(s: ImmutableString) -> i64 { s.len() as i64 } // <- the above is equivalent to this
|
||||||
|
|
||||||
@ -20,3 +28,23 @@ let len = engine.eval::<i64>("x.len1()")?; // error: function '
|
|||||||
let len = engine.eval::<i64>("x.len2()")?; // works fine
|
let len = engine.eval::<i64>("x.len2()")?; // works fine
|
||||||
let len = engine.eval::<i64>("x.len3()")?; // works fine
|
let len = engine.eval::<i64>("x.len3()")?; // works fine
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Avoid `&mut ImmutableString`
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
Rhai functions can take a first `&mut` parameter. Usually this is a good idea because it avoids
|
||||||
|
cloning of the argument (except for primary types where cloning is cheap), so its use is encouraged
|
||||||
|
even though there is no intention to ever mutate that argument.
|
||||||
|
|
||||||
|
`ImmutableString` is an exception to this rule. While `ImmutableString` is cheap to clone (only
|
||||||
|
incrementing a reference count), taking a mutable reference to it involves making a private clone
|
||||||
|
of the underlying string because Rhai has no way to find out whether that parameter will be mutated.
|
||||||
|
|
||||||
|
If the `ImmutableString` is not shared by any other variables, then Rhai just returns a mutable
|
||||||
|
reference to it since nobody else is watching! Otherwise a private copy is made first,
|
||||||
|
because other reference holders will not expect the `ImmutableString` to ever change
|
||||||
|
(it is supposed to be _immutable_).
|
||||||
|
|
||||||
|
Therefore, avoid using `&mut ImmutableString` as the first parameter of a function unless you really
|
||||||
|
intend to mutate that string. Use `ImmutableString` instead.
|
||||||
|
@ -3,8 +3,8 @@ Maximum Size of Arrays
|
|||||||
|
|
||||||
{{#include ../links.md}}
|
{{#include ../links.md}}
|
||||||
|
|
||||||
Limiting How Large Arrays Can Grow
|
Limit How Large Arrays Can Grow
|
||||||
---------------------------------
|
------------------------------
|
||||||
|
|
||||||
Rhai by default does not limit how large an [array] can be.
|
Rhai by default does not limit how large an [array] can be.
|
||||||
|
|
||||||
|
@ -3,8 +3,8 @@ Maximum Call Stack Depth
|
|||||||
|
|
||||||
{{#include ../links.md}}
|
{{#include ../links.md}}
|
||||||
|
|
||||||
Limiting How Stack Usage by Scripts
|
Limit How Stack Usage by Scripts
|
||||||
----------------------------------
|
-------------------------------
|
||||||
|
|
||||||
Rhai by default limits function calls to a maximum depth of 128 levels (16 levels in debug build).
|
Rhai by default limits function calls to a maximum depth of 128 levels (16 levels in debug build).
|
||||||
|
|
||||||
|
@ -3,8 +3,8 @@ Maximum Size of Object Maps
|
|||||||
|
|
||||||
{{#include ../links.md}}
|
{{#include ../links.md}}
|
||||||
|
|
||||||
Limiting How Large Object Maps Can Grow
|
Limit How Large Object Maps Can Grow
|
||||||
--------------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
Rhai by default does not limit how large (i.e. the number of properties) an [object map] can be.
|
Rhai by default does not limit how large (i.e. the number of properties) an [object map] can be.
|
||||||
|
|
||||||
|
@ -3,8 +3,8 @@ Maximum Number of Operations
|
|||||||
|
|
||||||
{{#include ../links.md}}
|
{{#include ../links.md}}
|
||||||
|
|
||||||
Limiting How Long a Script Can Run
|
Limit How Long a Script Can Run
|
||||||
---------------------------------
|
------------------------------
|
||||||
|
|
||||||
Rhai by default does not limit how much time or CPU a script consumes.
|
Rhai by default does not limit how much time or CPU a script consumes.
|
||||||
|
|
||||||
|
@ -3,8 +3,8 @@ Maximum Statement Depth
|
|||||||
|
|
||||||
{{#include ../links.md}}
|
{{#include ../links.md}}
|
||||||
|
|
||||||
Limiting How Deeply-Nested a Statement Can Be
|
Limit How Deeply-Nested a Statement Can Be
|
||||||
--------------------------------------------
|
-----------------------------------------
|
||||||
|
|
||||||
Rhai by default limits statements and expressions nesting to a maximum depth of 128
|
Rhai by default limits statements and expressions nesting to a maximum depth of 128
|
||||||
(which should be plenty) when they are at _global_ level, but only a depth of 32
|
(which should be plenty) when they are at _global_ level, but only a depth of 32
|
||||||
|
@ -3,8 +3,8 @@ Maximum Length of Strings
|
|||||||
|
|
||||||
{{#include ../links.md}}
|
{{#include ../links.md}}
|
||||||
|
|
||||||
Limiting How Long Strings Can Grow
|
Limit How Long Strings Can Grow
|
||||||
---------------------------------
|
------------------------------
|
||||||
|
|
||||||
Rhai by default does not limit how long a [string] can be.
|
Rhai by default does not limit how long a [string] can be.
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
Tracking Progress and Force-Termination
|
Track Progress and Force-Termination
|
||||||
======================================
|
===================================
|
||||||
|
|
||||||
{{#include ../links.md}}
|
{{#include ../links.md}}
|
||||||
|
|
||||||
|
@ -29,3 +29,16 @@ Turning on [`no_float`], and [`only_i32`] makes the key [`Dynamic`] data type on
|
|||||||
while normally it can be up to 16 bytes (e.g. on x86/x64 CPU's) in order to hold an `i64` or `f64`.
|
while normally it can be up to 16 bytes (e.g. on x86/x64 CPU's) in order to hold an `i64` or `f64`.
|
||||||
|
|
||||||
Making [`Dynamic`] small helps performance due to better cache efficiency.
|
Making [`Dynamic`] small helps performance due to better cache efficiency.
|
||||||
|
|
||||||
|
|
||||||
|
Use `ImmutableString`
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Internally, Rhai uses _immutable_ [strings] instead of the Rust `String` type. This is mainly to avoid excessive
|
||||||
|
cloning when passing function arguments.
|
||||||
|
|
||||||
|
The encapsulated immutable string type is `ImmutableString`. It is cheap to clone (just an `Rc` or `Arc` reference
|
||||||
|
count increment depending on the [`sync`] feature).
|
||||||
|
|
||||||
|
Therefore, functions taking `String` parameters should use `ImmutableString` or `&str` (which maps to `ImmutableString`)
|
||||||
|
for the best performance with Rhai.
|
||||||
|
@ -10,7 +10,7 @@ A number of examples can be found in the `examples` folder:
|
|||||||
| [`arrays_and_structs`](https://github.com/jonathandturner/rhai/tree/master/examples/arrays_and_structs.rs) | Shows how to register a custom Rust type and using [arrays] on it. |
|
| [`arrays_and_structs`](https://github.com/jonathandturner/rhai/tree/master/examples/arrays_and_structs.rs) | Shows how to register a custom Rust type and using [arrays] on it. |
|
||||||
| [`custom_types_and_methods`](https://github.com/jonathandturner/rhai/tree/master/examples/custom_types_and_methods.rs) | Shows how to register a custom Rust type and methods for it. |
|
| [`custom_types_and_methods`](https://github.com/jonathandturner/rhai/tree/master/examples/custom_types_and_methods.rs) | Shows how to register a custom Rust type and methods for it. |
|
||||||
| [`hello`](https://github.com/jonathandturner/rhai/tree/master/examples/hello.rs) | Simple example that evaluates an expression and prints the result. |
|
| [`hello`](https://github.com/jonathandturner/rhai/tree/master/examples/hello.rs) | Simple example that evaluates an expression and prints the result. |
|
||||||
| [`no_std`](https://github.com/jonathandturner/rhai/tree/master/examples/no_std.rs) | Example to test out `no-std` builds. |
|
| [`no_std`](https://github.com/jonathandturner/rhai/tree/master/examples/no_std.rs) | Example to test out `no-std` builds.</br>The [`no_std`] feature is required to build in `no-std`. |
|
||||||
| [`reuse_scope`](https://github.com/jonathandturner/rhai/tree/master/examples/reuse_scope.rs) | Evaluates two pieces of code in separate runs, but using a common [`Scope`]. |
|
| [`reuse_scope`](https://github.com/jonathandturner/rhai/tree/master/examples/reuse_scope.rs) | Evaluates two pieces of code in separate runs, but using a common [`Scope`]. |
|
||||||
| [`rhai_runner`](https://github.com/jonathandturner/rhai/tree/master/examples/rhai_runner.rs) | Runs each filename passed to it as a Rhai script. |
|
| [`rhai_runner`](https://github.com/jonathandturner/rhai/tree/master/examples/rhai_runner.rs) | Runs each filename passed to it as a Rhai script. |
|
||||||
| [`serde`](https://github.com/jonathandturner/rhai/tree/master/examples/serde.rs) | Example to serialize and deserialize Rust types with [`serde`](https://crates.io/crates/serde).<br/>The [`serde`] feature is required to run. |
|
| [`serde`](https://github.com/jonathandturner/rhai/tree/master/examples/serde.rs) | Example to serialize and deserialize Rust types with [`serde`](https://crates.io/crates/serde).<br/>The [`serde`] feature is required to run. |
|
||||||
|
@ -212,9 +212,11 @@ pub(crate) fn map_std_type_name(name: &str) -> &str {
|
|||||||
"string"
|
"string"
|
||||||
} else if name == type_name::<FnPtr>() {
|
} else if name == type_name::<FnPtr>() {
|
||||||
"Fn"
|
"Fn"
|
||||||
} else if name == type_name::<Instant>() {
|
|
||||||
"timestamp"
|
|
||||||
} else {
|
} else {
|
||||||
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
if name == type_name::<Instant>() {
|
||||||
|
return "timestamp";
|
||||||
|
}
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
if name == type_name::<Array>() {
|
if name == type_name::<Array>() {
|
||||||
return "array";
|
return "array";
|
||||||
|
@ -26,7 +26,7 @@ use crate::stdlib::{
|
|||||||
boxed::Box,
|
boxed::Box,
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
convert::TryFrom,
|
convert::TryFrom,
|
||||||
format,
|
fmt, format,
|
||||||
iter::{empty, once},
|
iter::{empty, once},
|
||||||
mem,
|
mem,
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
@ -96,6 +96,7 @@ pub const MARKER_BLOCK: &str = "$block$";
|
|||||||
pub const MARKER_IDENT: &str = "$ident$";
|
pub const MARKER_IDENT: &str = "$ident$";
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub struct Expression<'a>(&'a Expr);
|
pub struct Expression<'a>(&'a Expr);
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
@ -346,6 +347,15 @@ pub struct Engine {
|
|||||||
pub(crate) max_map_size: usize,
|
pub(crate) max_map_size: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Engine {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self.id.as_ref() {
|
||||||
|
Some(id) => write!(f, "Engine({})", id),
|
||||||
|
None => f.write_str("Engine"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Engine {
|
impl Default for Engine {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
// Create the new scripting Engine
|
// Create the new scripting Engine
|
||||||
@ -855,7 +865,7 @@ impl Engine {
|
|||||||
fn_name,
|
fn_name,
|
||||||
args.iter()
|
args.iter()
|
||||||
.map(|name| if name.is::<ImmutableString>() {
|
.map(|name| if name.is::<ImmutableString>() {
|
||||||
"&str | ImmutableString"
|
"&str | ImmutableString | String"
|
||||||
} else {
|
} else {
|
||||||
self.map_type_name((*name).type_name())
|
self.map_type_name((*name).type_name())
|
||||||
})
|
})
|
||||||
@ -934,7 +944,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Has a system function an override?
|
// Has a system function an override?
|
||||||
fn has_override(&self, lib: &Module, (hash_fn, hash_script): (u64, u64)) -> bool {
|
fn has_override(&self, lib: &Module, hash_fn: u64, hash_script: u64) -> bool {
|
||||||
// NOTE: We skip script functions for global_module and packages, and native functions for lib
|
// NOTE: We skip script functions for global_module and packages, and native functions for lib
|
||||||
|
|
||||||
// First check script-defined functions
|
// First check script-defined functions
|
||||||
@ -976,13 +986,15 @@ impl Engine {
|
|||||||
|
|
||||||
match fn_name {
|
match fn_name {
|
||||||
// type_of
|
// type_of
|
||||||
KEYWORD_TYPE_OF if args.len() == 1 && !self.has_override(lib, hashes) => Ok((
|
KEYWORD_TYPE_OF if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => {
|
||||||
|
Ok((
|
||||||
self.map_type_name(args[0].type_name()).to_string().into(),
|
self.map_type_name(args[0].type_name()).to_string().into(),
|
||||||
false,
|
false,
|
||||||
)),
|
))
|
||||||
|
}
|
||||||
|
|
||||||
// Fn
|
// Fn
|
||||||
KEYWORD_FN_PTR if args.len() == 1 && !self.has_override(lib, hashes) => {
|
KEYWORD_FN_PTR if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => {
|
||||||
Err(Box::new(EvalAltResult::ErrorRuntime(
|
Err(Box::new(EvalAltResult::ErrorRuntime(
|
||||||
"'Fn' should not be called in method style. Try Fn(...);".into(),
|
"'Fn' should not be called in method style. Try Fn(...);".into(),
|
||||||
Position::none(),
|
Position::none(),
|
||||||
@ -990,7 +1002,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// eval - reaching this point it must be a method-style call
|
// eval - reaching this point it must be a method-style call
|
||||||
KEYWORD_EVAL if args.len() == 1 && !self.has_override(lib, hashes) => {
|
KEYWORD_EVAL if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => {
|
||||||
Err(Box::new(EvalAltResult::ErrorRuntime(
|
Err(Box::new(EvalAltResult::ErrorRuntime(
|
||||||
"'eval' should not be called in method style. Try eval(...);".into(),
|
"'eval' should not be called in method style. Try eval(...);".into(),
|
||||||
Position::none(),
|
Position::none(),
|
||||||
@ -1076,10 +1088,10 @@ impl Engine {
|
|||||||
let idx = idx_val.downcast_mut::<StaticVec<Dynamic>>().unwrap();
|
let idx = idx_val.downcast_mut::<StaticVec<Dynamic>>().unwrap();
|
||||||
let mut fn_name = name.as_ref();
|
let mut fn_name = name.as_ref();
|
||||||
|
|
||||||
// Check if it is a FnPtr call
|
|
||||||
let (result, updated) = if fn_name == KEYWORD_FN_PTR_CALL && obj.is::<FnPtr>() {
|
let (result, updated) = if fn_name == KEYWORD_FN_PTR_CALL && obj.is::<FnPtr>() {
|
||||||
|
// FnPtr call
|
||||||
// Redirect function name
|
// Redirect function name
|
||||||
fn_name = obj.as_str().unwrap();
|
let fn_name = obj.as_str().unwrap();
|
||||||
// Recalculate hash
|
// Recalculate hash
|
||||||
let hash = calc_fn_hash(empty(), fn_name, idx.len(), empty());
|
let hash = calc_fn_hash(empty(), fn_name, idx.len(), empty());
|
||||||
// Arguments are passed as-is
|
// Arguments are passed as-is
|
||||||
@ -1090,11 +1102,32 @@ impl Engine {
|
|||||||
self.exec_fn_call(
|
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, def_val, level,
|
||||||
)
|
)
|
||||||
|
} else if fn_name == KEYWORD_FN_PTR_CALL && idx.len() > 0 && idx[0].is::<FnPtr>() {
|
||||||
|
// FnPtr call on object
|
||||||
|
// Redirect function name
|
||||||
|
let fn_name = idx[0]
|
||||||
|
.downcast_ref::<FnPtr>()
|
||||||
|
.unwrap()
|
||||||
|
.get_fn_name()
|
||||||
|
.clone();
|
||||||
|
// Recalculate hash
|
||||||
|
let hash = calc_fn_hash(empty(), &fn_name, idx.len() - 1, empty());
|
||||||
|
// Replace the first argument with the object pointer
|
||||||
|
let mut arg_values = once(obj)
|
||||||
|
.chain(idx.iter_mut().skip(1))
|
||||||
|
.collect::<StaticVec<_>>();
|
||||||
|
let args = arg_values.as_mut();
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
let redirected: Option<ImmutableString>;
|
let redirected: Option<ImmutableString>;
|
||||||
let mut hash = *hash;
|
let mut hash = *hash;
|
||||||
|
|
||||||
// Check if it is a map method call in OOP style
|
// Check if it is a map method call in OOP style
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
if let Some(map) = obj.downcast_ref::<Map>() {
|
if let Some(map) = obj.downcast_ref::<Map>() {
|
||||||
if let Some(val) = map.get(fn_name) {
|
if let Some(val) = map.get(fn_name) {
|
||||||
if let Some(f) = val.downcast_ref::<FnPtr>() {
|
if let Some(f) = val.downcast_ref::<FnPtr>() {
|
||||||
@ -1915,7 +1948,7 @@ impl Engine {
|
|||||||
let hash_fn =
|
let hash_fn =
|
||||||
calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
|
calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
|
||||||
|
|
||||||
if !self.has_override(lib, (hash_fn, *hash)) {
|
if !self.has_override(lib, hash_fn, *hash) {
|
||||||
// Fn - only in function call style
|
// Fn - only in function call style
|
||||||
let expr = args_expr.get(0);
|
let expr = args_expr.get(0);
|
||||||
let arg_value =
|
let arg_value =
|
||||||
@ -1941,7 +1974,7 @@ impl Engine {
|
|||||||
let hash_fn =
|
let hash_fn =
|
||||||
calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
|
calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
|
||||||
|
|
||||||
if !self.has_override(lib, (hash_fn, *hash)) {
|
if !self.has_override(lib, hash_fn, *hash) {
|
||||||
// eval - only in function call style
|
// eval - only in function call style
|
||||||
let prev_len = scope.len();
|
let prev_len = scope.len();
|
||||||
let expr = args_expr.get(0);
|
let expr = args_expr.get(0);
|
||||||
@ -1961,6 +1994,36 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle call() - Redirect function call
|
||||||
|
let redirected;
|
||||||
|
let mut name = name.as_ref();
|
||||||
|
let mut args_expr = args_expr.as_ref();
|
||||||
|
let mut hash = *hash;
|
||||||
|
|
||||||
|
if name == KEYWORD_FN_PTR_CALL
|
||||||
|
&& args_expr.len() >= 1
|
||||||
|
&& !self.has_override(lib, 0, hash)
|
||||||
|
{
|
||||||
|
let expr = args_expr.get(0).unwrap();
|
||||||
|
let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||||
|
|
||||||
|
if fn_ptr.is::<FnPtr>() {
|
||||||
|
// Redirect function name
|
||||||
|
redirected = Some(fn_ptr.cast::<FnPtr>().take_fn_name());
|
||||||
|
name = redirected.as_ref().unwrap();
|
||||||
|
// Skip the first argument
|
||||||
|
args_expr = &args_expr.as_ref()[1..];
|
||||||
|
// Recalculate hash
|
||||||
|
hash = calc_fn_hash(empty(), name, args_expr.len(), empty());
|
||||||
|
} else {
|
||||||
|
return Err(Box::new(EvalAltResult::ErrorMismatchOutputType(
|
||||||
|
self.map_type_name(type_name::<FnPtr>()).into(),
|
||||||
|
fn_ptr.type_name().into(),
|
||||||
|
expr.position(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Normal function call - except for Fn and eval (handled above)
|
// Normal function call - except for Fn and eval (handled above)
|
||||||
let mut arg_values: StaticVec<Dynamic>;
|
let mut arg_values: StaticVec<Dynamic>;
|
||||||
let mut args: StaticVec<_>;
|
let mut args: StaticVec<_>;
|
||||||
@ -1972,7 +2035,7 @@ impl Engine {
|
|||||||
} else {
|
} else {
|
||||||
// See if the first argument is a variable, if so, convert to method-call style
|
// 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
|
// in order to leverage potential &mut first argument and avoid cloning the value
|
||||||
match args_expr.get(0) {
|
match args_expr.get(0).unwrap() {
|
||||||
// func(x, ...) -> x.func(...)
|
// func(x, ...) -> x.func(...)
|
||||||
lhs @ Expr::Variable(_) => {
|
lhs @ Expr::Variable(_) => {
|
||||||
arg_values = args_expr
|
arg_values = args_expr
|
||||||
@ -2009,7 +2072,7 @@ impl Engine {
|
|||||||
|
|
||||||
let args = args.as_mut();
|
let args = args.as_mut();
|
||||||
self.exec_fn_call(
|
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, def_val, level,
|
||||||
)
|
)
|
||||||
.map(|(v, _)| v)
|
.map(|(v, _)| v)
|
||||||
.map_err(|err| err.new_position(*pos))
|
.map_err(|err| err.new_position(*pos))
|
||||||
|
@ -98,6 +98,8 @@ pub enum ParseErrorType {
|
|||||||
PropertyExpected,
|
PropertyExpected,
|
||||||
/// Missing a variable name after the `let`, `const` or `for` keywords.
|
/// Missing a variable name after the `let`, `const` or `for` keywords.
|
||||||
VariableExpected,
|
VariableExpected,
|
||||||
|
/// An identifier is a reserved keyword.
|
||||||
|
Reserved(String),
|
||||||
/// Missing an expression. Wrapped value is the expression type.
|
/// Missing an expression. Wrapped value is the expression type.
|
||||||
ExprExpected(String),
|
ExprExpected(String),
|
||||||
/// Defining a function `fn` in an appropriate place (e.g. inside another function).
|
/// Defining a function `fn` in an appropriate place (e.g. inside another function).
|
||||||
@ -163,6 +165,7 @@ impl ParseErrorType {
|
|||||||
Self::ForbiddenConstantExpr(_) => "Expecting a constant",
|
Self::ForbiddenConstantExpr(_) => "Expecting a constant",
|
||||||
Self::PropertyExpected => "Expecting name of a property",
|
Self::PropertyExpected => "Expecting name of a property",
|
||||||
Self::VariableExpected => "Expecting name of a variable",
|
Self::VariableExpected => "Expecting name of a variable",
|
||||||
|
Self::Reserved(_) => "Invalid use of reserved keyword",
|
||||||
Self::ExprExpected(_) => "Expecting an expression",
|
Self::ExprExpected(_) => "Expecting an expression",
|
||||||
Self::FnMissingName => "Expecting name in function declaration",
|
Self::FnMissingName => "Expecting name in function declaration",
|
||||||
Self::FnMissingParams(_) => "Expecting parameters in function declaration",
|
Self::FnMissingParams(_) => "Expecting parameters in function declaration",
|
||||||
@ -224,6 +227,7 @@ impl fmt::Display for ParseErrorType {
|
|||||||
Self::LiteralTooLarge(typ, max) => {
|
Self::LiteralTooLarge(typ, max) => {
|
||||||
write!(f, "{} exceeds the maximum limit ({})", typ, max)
|
write!(f, "{} exceeds the maximum limit ({})", typ, max)
|
||||||
}
|
}
|
||||||
|
Self::Reserved(s) => write!(f, "'{}' is a reserved keyword", s),
|
||||||
_ => f.write_str(self.desc()),
|
_ => f.write_str(self.desc()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ use crate::result::EvalAltResult;
|
|||||||
use crate::token::{is_valid_identifier, Position};
|
use crate::token::{is_valid_identifier, Position};
|
||||||
use crate::utils::ImmutableString;
|
use crate::utils::ImmutableString;
|
||||||
|
|
||||||
use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, rc::Rc, sync::Arc};
|
use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, rc::Rc, string::String, sync::Arc};
|
||||||
|
|
||||||
/// Trait that maps to `Send + Sync` only under the `sync` feature.
|
/// Trait that maps to `Send + Sync` only under the `sync` feature.
|
||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
@ -87,7 +87,7 @@ impl TryFrom<ImmutableString> for FnPtr {
|
|||||||
Ok(Self(value))
|
Ok(Self(value))
|
||||||
} else {
|
} else {
|
||||||
Err(Box::new(EvalAltResult::ErrorFunctionNotFound(
|
Err(Box::new(EvalAltResult::ErrorFunctionNotFound(
|
||||||
value.to_string(),
|
value.into(),
|
||||||
Position::none(),
|
Position::none(),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,16 @@ use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync};
|
|||||||
use crate::module::Module;
|
use crate::module::Module;
|
||||||
use crate::parser::FnAccess;
|
use crate::parser::FnAccess;
|
||||||
use crate::plugin::Plugin;
|
use crate::plugin::Plugin;
|
||||||
|
use crate::r#unsafe::unsafe_cast_box;
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
use crate::utils::ImmutableString;
|
use crate::utils::ImmutableString;
|
||||||
|
|
||||||
use crate::stdlib::{any::TypeId, boxed::Box, mem};
|
use crate::stdlib::{
|
||||||
|
any::TypeId,
|
||||||
|
boxed::Box,
|
||||||
|
mem,
|
||||||
|
string::{String, ToString},
|
||||||
|
};
|
||||||
|
|
||||||
/// A trait to register custom plugins with the `Engine`.
|
/// A trait to register custom plugins with the `Engine`.
|
||||||
///
|
///
|
||||||
@ -185,6 +191,9 @@ pub fn by_value<T: Variant + Clone>(data: &mut Dynamic) -> T {
|
|||||||
let ref_str = data.as_str().unwrap();
|
let ref_str = data.as_str().unwrap();
|
||||||
let ref_T = unsafe { mem::transmute::<_, &T>(&ref_str) };
|
let ref_T = unsafe { mem::transmute::<_, &T>(&ref_str) };
|
||||||
ref_T.clone()
|
ref_T.clone()
|
||||||
|
} else if TypeId::of::<T>() == TypeId::of::<String>() {
|
||||||
|
// If T is String, data must be ImmutableString, so map directly to it
|
||||||
|
*unsafe_cast_box(Box::new(data.as_str().unwrap().to_string())).unwrap()
|
||||||
} else {
|
} else {
|
||||||
// We consume the argument and then replace it with () - the argument is not supposed to be used again.
|
// We consume the argument and then replace it with () - the argument is not supposed to be used again.
|
||||||
// This way, we avoid having to clone the argument again, because it is already a clone when passed here.
|
// This way, we avoid having to clone the argument again, because it is already a clone when passed here.
|
||||||
@ -240,13 +249,15 @@ pub fn map_result(
|
|||||||
data
|
data
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remap `&str` to `ImmutableString`.
|
/// Remap `&str` | `String` to `ImmutableString`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn map_type_id<T: 'static>() -> TypeId {
|
fn map_type_id<T: 'static>() -> TypeId {
|
||||||
let id = TypeId::of::<T>();
|
let id = TypeId::of::<T>();
|
||||||
|
|
||||||
if id == TypeId::of::<&str>() {
|
if id == TypeId::of::<&str>() {
|
||||||
TypeId::of::<ImmutableString>()
|
TypeId::of::<ImmutableString>()
|
||||||
|
} else if id == TypeId::of::<String>() {
|
||||||
|
TypeId::of::<ImmutableString>()
|
||||||
} else {
|
} else {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
@ -25,11 +25,13 @@ use crate::stdlib::{
|
|||||||
num::NonZeroUsize,
|
num::NonZeroUsize,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
sync::RwLock,
|
|
||||||
vec,
|
vec,
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
use crate::stdlib::sync::RwLock;
|
||||||
|
|
||||||
/// Return type of module-level Rust function.
|
/// Return type of module-level Rust function.
|
||||||
pub type FuncReturn<T> = Result<T, Box<EvalAltResult>>;
|
pub type FuncReturn<T> = Result<T, Box<EvalAltResult>>;
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ pub type PackageLibrary = Shared<Module>;
|
|||||||
|
|
||||||
/// Type containing a collection of `PackageLibrary` instances.
|
/// Type containing a collection of `PackageLibrary` instances.
|
||||||
/// All function and type iterator keys in the loaded packages are indexed for fast access.
|
/// All function and type iterator keys in the loaded packages are indexed for fast access.
|
||||||
#[derive(Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub(crate) struct PackagesCollection(StaticVec<PackageLibrary>);
|
pub(crate) struct PackagesCollection(StaticVec<PackageLibrary>);
|
||||||
|
|
||||||
impl PackagesCollection {
|
impl PackagesCollection {
|
||||||
|
122
src/parser.rs
122
src/parser.rs
@ -7,7 +7,7 @@ use crate::error::{LexError, ParseError, ParseErrorType};
|
|||||||
use crate::module::{Module, ModuleRef};
|
use crate::module::{Module, ModuleRef};
|
||||||
use crate::optimize::{optimize_into_ast, OptimizationLevel};
|
use crate::optimize::{optimize_into_ast, OptimizationLevel};
|
||||||
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
||||||
use crate::token::{Position, Token, TokenStream};
|
use crate::token::{is_valid_identifier, Position, Token, TokenStream};
|
||||||
use crate::utils::{StaticVec, StraightHasherBuilder};
|
use crate::utils::{StaticVec, StraightHasherBuilder};
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
@ -25,6 +25,7 @@ use crate::stdlib::{
|
|||||||
char,
|
char,
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fmt, format,
|
fmt, format,
|
||||||
|
hash::Hash,
|
||||||
iter::empty,
|
iter::empty,
|
||||||
mem,
|
mem,
|
||||||
num::NonZeroUsize,
|
num::NonZeroUsize,
|
||||||
@ -340,7 +341,7 @@ impl fmt::Display for FnAccess {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A scripted function definition.
|
/// A scripted function definition.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub struct ScriptFnDef {
|
pub struct ScriptFnDef {
|
||||||
/// Function name.
|
/// Function name.
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@ -374,7 +375,7 @@ impl fmt::Display for ScriptFnDef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `return`/`throw` statement.
|
/// `return`/`throw` statement.
|
||||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
|
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
|
||||||
pub enum ReturnType {
|
pub enum ReturnType {
|
||||||
/// `return` statement.
|
/// `return` statement.
|
||||||
Return,
|
Return,
|
||||||
@ -440,6 +441,8 @@ struct ParseSettings {
|
|||||||
pos: Position,
|
pos: Position,
|
||||||
/// Is the construct being parsed located at global level?
|
/// Is the construct being parsed located at global level?
|
||||||
is_global: bool,
|
is_global: bool,
|
||||||
|
/// Is the construct being parsed located at function definition level?
|
||||||
|
is_function_scope: bool,
|
||||||
/// Is the current position inside a loop?
|
/// Is the current position inside a loop?
|
||||||
is_breakable: bool,
|
is_breakable: bool,
|
||||||
/// Is anonymous function allowed?
|
/// Is anonymous function allowed?
|
||||||
@ -476,7 +479,7 @@ impl ParseSettings {
|
|||||||
///
|
///
|
||||||
/// Each variant is at most one pointer in size (for speed),
|
/// Each variant is at most one pointer in size (for speed),
|
||||||
/// with everything being allocated together in one single tuple.
|
/// with everything being allocated together in one single tuple.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub enum Stmt {
|
pub enum Stmt {
|
||||||
/// No-op.
|
/// No-op.
|
||||||
Noop(Position),
|
Noop(Position),
|
||||||
@ -588,6 +591,13 @@ impl fmt::Debug for CustomExpr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "internals")]
|
||||||
|
impl Hash for CustomExpr {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.0.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An expression.
|
/// An expression.
|
||||||
///
|
///
|
||||||
/// Each variant is at most one pointer in size (for speed),
|
/// Each variant is at most one pointer in size (for speed),
|
||||||
@ -663,6 +673,18 @@ impl Default for Expr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Hash for Expr {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
match self {
|
||||||
|
Self::FloatConstant(x) => {
|
||||||
|
state.write(&x.0.to_le_bytes());
|
||||||
|
x.1.hash(state);
|
||||||
|
}
|
||||||
|
_ => self.hash(state),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Expr {
|
impl Expr {
|
||||||
/// Get the `Dynamic` value of a constant expression.
|
/// Get the `Dynamic` value of a constant expression.
|
||||||
///
|
///
|
||||||
@ -1343,24 +1365,27 @@ fn parse_map_literal(
|
|||||||
eat_token(input, Token::RightBrace);
|
eat_token(input, Token::RightBrace);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
let (name, pos) = match input.next().unwrap() {
|
let (name, pos) = match input.next().unwrap() {
|
||||||
(Token::Identifier(s), pos) => (s, pos),
|
(Token::Identifier(s), pos) => (s, pos),
|
||||||
(Token::StringConstant(s), pos) => (s, pos),
|
(Token::StringConstant(s), pos) => (s, pos),
|
||||||
|
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
||||||
|
return Err(PERR::Reserved(s).into_err(pos));
|
||||||
|
}
|
||||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||||
(_, pos) if map.is_empty() => {
|
(_, pos) if map.is_empty() => {
|
||||||
return Err(PERR::MissingToken(
|
return Err(
|
||||||
Token::RightBrace.into(),
|
PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into())
|
||||||
MISSING_RBRACE.into(),
|
.into_err(pos),
|
||||||
)
|
);
|
||||||
.into_err(pos))
|
|
||||||
}
|
}
|
||||||
(Token::EOF, pos) => {
|
(Token::EOF, pos) => {
|
||||||
return Err(PERR::MissingToken(
|
return Err(
|
||||||
Token::RightBrace.into(),
|
PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into())
|
||||||
MISSING_RBRACE.into(),
|
.into_err(pos),
|
||||||
)
|
);
|
||||||
.into_err(pos))
|
|
||||||
}
|
}
|
||||||
(_, pos) => return Err(PERR::PropertyExpected.into_err(pos)),
|
(_, pos) => return Err(PERR::PropertyExpected.into_err(pos)),
|
||||||
};
|
};
|
||||||
@ -1390,8 +1415,6 @@ fn parse_map_literal(
|
|||||||
|
|
||||||
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
||||||
map.push(((Into::<ImmutableString>::into(name), pos), expr));
|
map.push(((Into::<ImmutableString>::into(name), pos), expr));
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match input.peek().unwrap() {
|
match input.peek().unwrap() {
|
||||||
(Token::Comma, _) => {
|
(Token::Comma, _) => {
|
||||||
@ -1460,6 +1483,24 @@ fn parse_primary(
|
|||||||
let index = state.find_var(&s);
|
let index = state.find_var(&s);
|
||||||
Expr::Variable(Box::new(((s, settings.pos), None, 0, index)))
|
Expr::Variable(Box::new(((s, settings.pos), None, 0, index)))
|
||||||
}
|
}
|
||||||
|
// Function call is allowed to have reserved keyword
|
||||||
|
Token::Reserved(s) if s != KEYWORD_THIS && input.peek().unwrap().0 == Token::LeftParen => {
|
||||||
|
Expr::Variable(Box::new(((s, settings.pos), None, 0, None)))
|
||||||
|
}
|
||||||
|
// Access to `this` as a variable is OK
|
||||||
|
Token::Reserved(s) if s == KEYWORD_THIS && input.peek().unwrap().0 != Token::LeftParen => {
|
||||||
|
if !settings.is_function_scope {
|
||||||
|
return Err(
|
||||||
|
PERR::BadInput(format!("'{}' can only be used in functions", s))
|
||||||
|
.into_err(settings.pos),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Expr::Variable(Box::new(((s, settings.pos), None, 0, None)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Token::Reserved(s) if is_valid_identifier(s.chars()) => {
|
||||||
|
return Err(PERR::Reserved(s).into_err(settings.pos));
|
||||||
|
}
|
||||||
Token::LeftParen => parse_paren_expr(input, state, lib, settings.level_up())?,
|
Token::LeftParen => parse_paren_expr(input, state, lib, settings.level_up())?,
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Token::LeftBracket => parse_array_literal(input, state, lib, settings.level_up())?,
|
Token::LeftBracket => parse_array_literal(input, state, lib, settings.level_up())?,
|
||||||
@ -1471,7 +1512,7 @@ fn parse_primary(
|
|||||||
_ => {
|
_ => {
|
||||||
return Err(
|
return Err(
|
||||||
PERR::BadInput(format!("Unexpected '{}'", token.syntax())).into_err(settings.pos)
|
PERR::BadInput(format!("Unexpected '{}'", token.syntax())).into_err(settings.pos)
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1509,6 +1550,9 @@ fn parse_primary(
|
|||||||
|
|
||||||
Expr::Variable(Box::new(((id2, pos2), modules, 0, index)))
|
Expr::Variable(Box::new(((id2, pos2), modules, 0, index)))
|
||||||
}
|
}
|
||||||
|
(Token::Reserved(id2), pos2) if is_valid_identifier(id2.chars()) => {
|
||||||
|
return Err(PERR::Reserved(id2).into_err(pos2));
|
||||||
|
}
|
||||||
(_, pos2) => return Err(PERR::VariableExpected.into_err(pos2)),
|
(_, pos2) => return Err(PERR::VariableExpected.into_err(pos2)),
|
||||||
},
|
},
|
||||||
// Indexing
|
// Indexing
|
||||||
@ -1538,6 +1582,7 @@ fn parse_primary(
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure identifiers are valid
|
||||||
Ok(root_expr)
|
Ok(root_expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2094,6 +2139,9 @@ fn parse_expr(
|
|||||||
(Token::Identifier(s), pos) => {
|
(Token::Identifier(s), pos) => {
|
||||||
exprs.push(Expr::Variable(Box::new(((s, pos), None, 0, None))));
|
exprs.push(Expr::Variable(Box::new(((s, pos), None, 0, None))));
|
||||||
}
|
}
|
||||||
|
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
||||||
|
return Err(PERR::Reserved(s).into_err(pos));
|
||||||
|
}
|
||||||
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
||||||
},
|
},
|
||||||
MARKER_EXPR => exprs.push(parse_expr(input, state, lib, settings)?),
|
MARKER_EXPR => exprs.push(parse_expr(input, state, lib, settings)?),
|
||||||
@ -2261,10 +2309,12 @@ fn parse_for(
|
|||||||
let name = match input.next().unwrap() {
|
let name = match input.next().unwrap() {
|
||||||
// Variable name
|
// Variable name
|
||||||
(Token::Identifier(s), _) => s,
|
(Token::Identifier(s), _) => s,
|
||||||
|
// Reserved keyword
|
||||||
|
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
||||||
|
return Err(PERR::Reserved(s).into_err(pos));
|
||||||
|
}
|
||||||
// Bad identifier
|
// Bad identifier
|
||||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||||
// EOF
|
|
||||||
(Token::EOF, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
|
||||||
// Not a variable name
|
// Not a variable name
|
||||||
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
||||||
};
|
};
|
||||||
@ -2311,20 +2361,13 @@ fn parse_let(
|
|||||||
// let name ...
|
// let name ...
|
||||||
let (name, pos) = match input.next().unwrap() {
|
let (name, pos) = match input.next().unwrap() {
|
||||||
(Token::Identifier(s), pos) => (s, pos),
|
(Token::Identifier(s), pos) => (s, pos),
|
||||||
|
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
||||||
|
return Err(PERR::Reserved(s).into_err(pos));
|
||||||
|
}
|
||||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||||
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if the name is allowed
|
|
||||||
match name.as_str() {
|
|
||||||
KEYWORD_THIS => {
|
|
||||||
return Err(
|
|
||||||
PERR::BadInput(LexError::MalformedIdentifier(name).to_string()).into_err(pos),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
// let name = ...
|
// let name = ...
|
||||||
if match_token(input, Token::Equals)? {
|
if match_token(input, Token::Equals)? {
|
||||||
// let name = expr
|
// let name = expr
|
||||||
@ -2390,6 +2433,9 @@ fn parse_import(
|
|||||||
// import expr as name ...
|
// import expr as name ...
|
||||||
let (name, _) = match input.next().unwrap() {
|
let (name, _) = match input.next().unwrap() {
|
||||||
(Token::Identifier(s), pos) => (s, pos),
|
(Token::Identifier(s), pos) => (s, pos),
|
||||||
|
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
||||||
|
return Err(PERR::Reserved(s).into_err(pos));
|
||||||
|
}
|
||||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||||
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
||||||
};
|
};
|
||||||
@ -2414,6 +2460,9 @@ fn parse_export(
|
|||||||
loop {
|
loop {
|
||||||
let (id, id_pos) = match input.next().unwrap() {
|
let (id, id_pos) = match input.next().unwrap() {
|
||||||
(Token::Identifier(s), pos) => (s.clone(), pos),
|
(Token::Identifier(s), pos) => (s.clone(), pos),
|
||||||
|
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
||||||
|
return Err(PERR::Reserved(s).into_err(pos));
|
||||||
|
}
|
||||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||||
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
||||||
};
|
};
|
||||||
@ -2421,6 +2470,10 @@ fn parse_export(
|
|||||||
let rename = if match_token(input, Token::As)? {
|
let rename = if match_token(input, Token::As)? {
|
||||||
match input.next().unwrap() {
|
match input.next().unwrap() {
|
||||||
(Token::Identifier(s), pos) => Some((s.clone(), pos)),
|
(Token::Identifier(s), pos) => Some((s.clone(), pos)),
|
||||||
|
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
||||||
|
return Err(PERR::Reserved(s).into_err(pos));
|
||||||
|
}
|
||||||
|
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||||
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -2598,6 +2651,7 @@ fn parse_stmt(
|
|||||||
allow_stmt_expr: true,
|
allow_stmt_expr: true,
|
||||||
allow_anonymous_fn: true,
|
allow_anonymous_fn: true,
|
||||||
is_global: false,
|
is_global: false,
|
||||||
|
is_function_scope: true,
|
||||||
is_breakable: false,
|
is_breakable: false,
|
||||||
level: 0,
|
level: 0,
|
||||||
pos: pos,
|
pos: pos,
|
||||||
@ -2695,7 +2749,11 @@ fn parse_fn(
|
|||||||
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
||||||
|
|
||||||
let name = match input.next().unwrap() {
|
let name = match input.next().unwrap() {
|
||||||
(Token::Identifier(s), _) | (Token::Custom(s), _) => s,
|
(Token::Identifier(s), _) | (Token::Custom(s), _) | (Token::Reserved(s), _)
|
||||||
|
if s != KEYWORD_THIS && is_valid_identifier(s.chars()) =>
|
||||||
|
{
|
||||||
|
s
|
||||||
|
}
|
||||||
(_, pos) => return Err(PERR::FnMissingName.into_err(pos)),
|
(_, pos) => return Err(PERR::FnMissingName.into_err(pos)),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2790,6 +2848,7 @@ impl Engine {
|
|||||||
allow_stmt_expr: false,
|
allow_stmt_expr: false,
|
||||||
allow_anonymous_fn: false,
|
allow_anonymous_fn: false,
|
||||||
is_global: true,
|
is_global: true,
|
||||||
|
is_function_scope: false,
|
||||||
is_breakable: false,
|
is_breakable: false,
|
||||||
level: 0,
|
level: 0,
|
||||||
pos: Position::none(),
|
pos: Position::none(),
|
||||||
@ -2829,6 +2888,7 @@ impl Engine {
|
|||||||
allow_stmt_expr: true,
|
allow_stmt_expr: true,
|
||||||
allow_anonymous_fn: true,
|
allow_anonymous_fn: true,
|
||||||
is_global: true,
|
is_global: true,
|
||||||
|
is_function_scope: false,
|
||||||
is_breakable: false,
|
is_breakable: false,
|
||||||
level: 0,
|
level: 0,
|
||||||
pos: Position::none(),
|
pos: Position::none(),
|
||||||
|
@ -4,6 +4,8 @@ use crate::optimize::OptimizationLevel;
|
|||||||
use crate::packages::PackageLibrary;
|
use crate::packages::PackageLibrary;
|
||||||
use crate::token::is_valid_identifier;
|
use crate::token::is_valid_identifier;
|
||||||
|
|
||||||
|
use crate::stdlib::{boxed::Box, format, string::String};
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
/// Load a new package into the `Engine`.
|
/// Load a new package into the `Engine`.
|
||||||
///
|
///
|
||||||
|
@ -16,7 +16,7 @@ mod inner {
|
|||||||
pub use core_error as error;
|
pub use core_error as error;
|
||||||
|
|
||||||
pub mod collections {
|
pub mod collections {
|
||||||
pub use hashbrown::HashMap;
|
pub use hashbrown::{HashMap, HashSet};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ impl Engine {
|
|||||||
) -> Result<Dynamic, Box<EvalAltResult>>
|
) -> Result<Dynamic, Box<EvalAltResult>>
|
||||||
+ SendSync
|
+ SendSync
|
||||||
+ 'static,
|
+ 'static,
|
||||||
) -> Result<self, Box<LexError>> {
|
) -> Result<&mut Self, Box<LexError>> {
|
||||||
if value.is_empty() {
|
if value.is_empty() {
|
||||||
return Err(Box::new(LexError::ImproperSymbol("".to_string())));
|
return Err(Box::new(LexError::ImproperSymbol("".to_string())));
|
||||||
}
|
}
|
||||||
|
34
src/token.rs
34
src/token.rs
@ -1,6 +1,10 @@
|
|||||||
//! Main module defining the lexer and parser.
|
//! Main module defining the lexer and parser.
|
||||||
|
|
||||||
use crate::engine::Engine;
|
use crate::engine::{
|
||||||
|
Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_PRINT,
|
||||||
|
KEYWORD_THIS, KEYWORD_TYPE_OF,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::error::LexError;
|
use crate::error::LexError;
|
||||||
use crate::parser::INT;
|
use crate::parser::INT;
|
||||||
use crate::utils::StaticVec;
|
use crate::utils::StaticVec;
|
||||||
@ -13,7 +17,7 @@ use crate::stdlib::{
|
|||||||
boxed::Box,
|
boxed::Box,
|
||||||
char,
|
char,
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fmt,
|
fmt, format,
|
||||||
iter::Peekable,
|
iter::Peekable,
|
||||||
str::{Chars, FromStr},
|
str::{Chars, FromStr},
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
@ -275,8 +279,6 @@ impl Token {
|
|||||||
Or => "||",
|
Or => "||",
|
||||||
Ampersand => "&",
|
Ampersand => "&",
|
||||||
And => "&&",
|
And => "&&",
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
Fn => "fn",
|
|
||||||
Continue => "continue",
|
Continue => "continue",
|
||||||
Break => "break",
|
Break => "break",
|
||||||
Return => "return",
|
Return => "return",
|
||||||
@ -297,8 +299,12 @@ impl Token {
|
|||||||
ModuloAssign => "%=",
|
ModuloAssign => "%=",
|
||||||
PowerOf => "~",
|
PowerOf => "~",
|
||||||
PowerOfAssign => "~=",
|
PowerOfAssign => "~=",
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
Fn => "fn",
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
Private => "private",
|
Private => "private",
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Import => "import",
|
Import => "import",
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
@ -355,8 +361,6 @@ impl Token {
|
|||||||
"||" => Or,
|
"||" => Or,
|
||||||
"&" => Ampersand,
|
"&" => Ampersand,
|
||||||
"&&" => And,
|
"&&" => And,
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
"fn" => Fn,
|
|
||||||
"continue" => Continue,
|
"continue" => Continue,
|
||||||
"break" => Break,
|
"break" => Break,
|
||||||
"return" => Return,
|
"return" => Return,
|
||||||
@ -377,17 +381,30 @@ impl Token {
|
|||||||
"%=" => ModuloAssign,
|
"%=" => ModuloAssign,
|
||||||
"~" => PowerOf,
|
"~" => PowerOf,
|
||||||
"~=" => PowerOfAssign,
|
"~=" => PowerOfAssign,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
"fn" => Fn,
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
"private" => Private,
|
"private" => Private,
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
"import" => Import,
|
"import" => Import,
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
"export" => Export,
|
"export" => Export,
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
"as" => As,
|
"as" => As,
|
||||||
|
|
||||||
|
#[cfg(feature = "no_function")]
|
||||||
|
"fn" | "private" => Reserved(syntax.into()),
|
||||||
|
|
||||||
|
#[cfg(feature = "no_module")]
|
||||||
|
"import" | "export" | "as" => Reserved(syntax.into()),
|
||||||
|
|
||||||
"===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" => {
|
"===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" => {
|
||||||
Reserved(syntax.into())
|
Reserved(syntax.into())
|
||||||
}
|
}
|
||||||
|
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
||||||
|
| KEYWORD_FN_PTR_CALL | KEYWORD_THIS => Reserved(syntax.into()),
|
||||||
|
|
||||||
_ => return None,
|
_ => return None,
|
||||||
})
|
})
|
||||||
@ -1353,9 +1370,10 @@ impl<'a> Iterator for TokenIterator<'a, '_> {
|
|||||||
"'#' is not a valid symbol. Should it be '#{'?"
|
"'#' is not a valid symbol. Should it be '#{'?"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
))),
|
))),
|
||||||
token => Token::LexError(Box::new(LERR::ImproperSymbol(
|
token if !is_valid_identifier(token.chars()) => Token::LexError(Box::new(LERR::ImproperSymbol(
|
||||||
format!("'{}' is not a valid symbol.", token)
|
format!("'{}' is a reserved symbol.", token)
|
||||||
))),
|
))),
|
||||||
|
_ => Token::Reserved(s)
|
||||||
}, pos)),
|
}, pos)),
|
||||||
(r @ Some(_), None, None) => r,
|
(r @ Some(_), None, None) => r,
|
||||||
(Some((Token::Identifier(s), pos)), _, Some(custom)) if custom.contains_key(&s) => {
|
(Some((Token::Identifier(s), pos)), _, Some(custom)) if custom.contains_key(&s) => {
|
||||||
|
@ -156,6 +156,12 @@ impl<T> Drop for StaticVec<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: Hash> Hash for StaticVec<T> {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.iter().for_each(|x| x.hash(state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> Default for StaticVec<T> {
|
impl<T> Default for StaticVec<T> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -115,7 +115,8 @@ fn test_anonymous_fn() -> Result<(), Box<EvalAltResult>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_fn_ptr() -> Result<(), Box<EvalAltResult>> {
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
fn test_fn_ptr_raw() -> Result<(), Box<EvalAltResult>> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
engine.register_raw_fn(
|
engine.register_raw_fn(
|
||||||
|
@ -8,7 +8,7 @@ fn test_decrement() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
*engine.eval::<String>(r#"let s = "test"; s -= "ing"; s"#).expect_err("expects error"),
|
*engine.eval::<String>(r#"let s = "test"; s -= "ing"; s"#).expect_err("expects error"),
|
||||||
EvalAltResult::ErrorFunctionNotFound(err, _) if err == "- (&str | ImmutableString, &str | ImmutableString)"
|
EvalAltResult::ErrorFunctionNotFound(err, _) if err == "- (&str | ImmutableString | String, &str | ImmutableString | String)"
|
||||||
));
|
));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
80
tests/fn_ptr.rs
Normal file
80
tests/fn_ptr.rs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
use rhai::{Engine, EvalAltResult, RegisterFn, INT};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fn_ptr() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
engine.register_fn("bar", |x: &mut INT, y: INT| *x += y);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
r#"
|
||||||
|
let f = Fn("bar");
|
||||||
|
let x = 40;
|
||||||
|
f.call(x, 2);
|
||||||
|
x
|
||||||
|
"#
|
||||||
|
)?,
|
||||||
|
40
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
r#"
|
||||||
|
let f = Fn("bar");
|
||||||
|
let x = 40;
|
||||||
|
x.call(f, 2);
|
||||||
|
x
|
||||||
|
"#
|
||||||
|
)?,
|
||||||
|
42
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
r#"
|
||||||
|
let f = Fn("bar");
|
||||||
|
let x = 40;
|
||||||
|
call(f, x, 2);
|
||||||
|
x
|
||||||
|
"#
|
||||||
|
)?,
|
||||||
|
42
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
r#"
|
||||||
|
fn foo(x) { this += x; }
|
||||||
|
|
||||||
|
let f = Fn("foo");
|
||||||
|
let x = 40;
|
||||||
|
x.call(f, 2);
|
||||||
|
x
|
||||||
|
"#
|
||||||
|
)?,
|
||||||
|
42
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
assert!(matches!(
|
||||||
|
*engine
|
||||||
|
.eval::<INT>(
|
||||||
|
r#"
|
||||||
|
fn foo(x) { this += x; }
|
||||||
|
|
||||||
|
let f = Fn("foo");
|
||||||
|
call(f, 2);
|
||||||
|
x
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.expect_err("should error"),
|
||||||
|
EvalAltResult::ErrorInFunctionCall(fn_name, err, _) if fn_name == "foo" && matches!(*err, EvalAltResult::ErrorUnboundedThis(_))
|
||||||
|
));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -52,12 +52,24 @@ fn test_functions() -> Result<(), Box<EvalAltResult>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(not(feature = "no_object"))]
|
|
||||||
fn test_function_pointers() -> Result<(), Box<EvalAltResult>> {
|
fn test_function_pointers() -> Result<(), Box<EvalAltResult>> {
|
||||||
let engine = Engine::new();
|
let engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(engine.eval::<String>(r#"type_of(Fn("abc"))"#)?, "Fn");
|
assert_eq!(engine.eval::<String>(r#"type_of(Fn("abc"))"#)?, "Fn");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
r#"
|
||||||
|
fn foo(x) { 40 + x }
|
||||||
|
|
||||||
|
let f = Fn("foo");
|
||||||
|
call(f, 2)
|
||||||
|
"#
|
||||||
|
)?,
|
||||||
|
42
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
r#"
|
r#"
|
||||||
@ -73,11 +85,13 @@ fn test_function_pointers() -> Result<(), Box<EvalAltResult>> {
|
|||||||
42
|
42
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
*engine.eval::<INT>(r#"let f = Fn("abc"); f.call(0)"#).expect_err("should error"),
|
*engine.eval::<INT>(r#"let f = Fn("abc"); f.call(0)"#).expect_err("should error"),
|
||||||
EvalAltResult::ErrorFunctionNotFound(f, _) if f.starts_with("abc (")
|
EvalAltResult::ErrorFunctionNotFound(f, _) if f.starts_with("abc (")
|
||||||
));
|
));
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
r#"
|
r#"
|
||||||
|
@ -173,17 +173,16 @@ fn test_string_fn() -> Result<(), Box<EvalAltResult>> {
|
|||||||
"foo"
|
"foo"
|
||||||
);
|
);
|
||||||
|
|
||||||
engine.register_fn("foo1", |s: &str| s.len() as INT);
|
engine
|
||||||
engine.register_fn("foo2", |s: ImmutableString| s.len() as INT);
|
.register_fn("foo1", |s: &str| s.len() as INT)
|
||||||
engine.register_fn("foo3", |s: String| s.len() as INT);
|
.register_fn("foo2", |s: ImmutableString| s.len() as INT)
|
||||||
|
.register_fn("foo3", |s: String| s.len() as INT)
|
||||||
|
.register_fn("foo4", |s: &mut ImmutableString| s.len() as INT);
|
||||||
|
|
||||||
assert_eq!(engine.eval::<INT>(r#"foo1("hello")"#)?, 5);
|
assert_eq!(engine.eval::<INT>(r#"foo1("hello")"#)?, 5);
|
||||||
assert_eq!(engine.eval::<INT>(r#"foo2("hello")"#)?, 5);
|
assert_eq!(engine.eval::<INT>(r#"foo2("hello")"#)?, 5);
|
||||||
|
assert_eq!(engine.eval::<INT>(r#"foo3("hello")"#)?, 5);
|
||||||
assert!(matches!(
|
assert_eq!(engine.eval::<INT>(r#"foo4("hello")"#)?, 5);
|
||||||
*engine.eval::<INT>(r#"foo3("hello")"#).expect_err("should error"),
|
|
||||||
EvalAltResult::ErrorFunctionNotFound(err, _) if err == "foo3 (&str | ImmutableString)"
|
|
||||||
));
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ fn test_tokens_custom_operator() -> Result<(), Box<EvalAltResult>> {
|
|||||||
15
|
15
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
r"
|
r"
|
||||||
|
Loading…
Reference in New Issue
Block a user