Merge branch 'master' into plugins
This commit is contained in:
commit
fa4d391e4b
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rhai"
|
||||
version = "0.13.0"
|
||||
version = "0.14.1"
|
||||
edition = "2018"
|
||||
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
|
||||
description = "Embedded scripting for Rust"
|
||||
@ -20,7 +20,7 @@ categories = [ "no-std", "embedded", "parser-implementations" ]
|
||||
num-traits = { version = "0.2.11", default-features = false }
|
||||
|
||||
[features]
|
||||
#default = ["no_stdlib", "no_function", "no_index", "no_object", "no_float", "only_i32", "unchecked", "no_optimize", "sync"]
|
||||
#default = ["no_stdlib", "no_function", "no_index", "no_object", "no_module", "no_float", "only_i32", "unchecked", "no_optimize", "sync"]
|
||||
default = []
|
||||
plugins = []
|
||||
unchecked = [] # unchecked arithmetic
|
||||
@ -29,6 +29,7 @@ no_float = [] # no floating-point
|
||||
no_function = [] # no script-defined functions
|
||||
no_object = [] # no custom objects
|
||||
no_optimize = [] # no script optimizer
|
||||
no_module = [] # no modules
|
||||
only_i32 = [] # set INT=i32 (useful for 32-bit systems)
|
||||
only_i64 = [] # set INT=i64 (default) and disable support for all other integer types
|
||||
sync = [] # restrict to only types that implement Send + Sync
|
||||
|
155
README.md
155
README.md
@ -15,7 +15,7 @@ Rhai's current features set:
|
||||
|
||||
* Easy-to-use language similar to JS+Rust
|
||||
* Easy integration with Rust [native functions](#working-with-functions) and [types](#custom-types-and-methods),
|
||||
including [getter/setter](#getters-and-setters)/[methods](#members-and-methods)
|
||||
including [getters/setters](#getters-and-setters), [methods](#members-and-methods) and [indexers](#indexers)
|
||||
* Easily [call a script-defined function](#calling-rhai-functions-from-rust) from Rust
|
||||
* Freely pass variables/constants into a script via an external [`Scope`]
|
||||
* Fairly efficient (1 million iterations in 0.75 sec on my 5 year old laptop)
|
||||
@ -23,13 +23,14 @@ Rhai's current features set:
|
||||
* [`no-std`](#optional-features) support
|
||||
* Support for [function overloading](#function-overloading)
|
||||
* Support for [operator overloading](#operator-overloading)
|
||||
* Support for loading external [modules]
|
||||
* Compiled script is [optimized](#script-optimization) for repeat evaluations
|
||||
* Support for [minimal builds](#minimal-builds) by excluding unneeded language [features](#optional-features)
|
||||
* Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/)
|
||||
to do checked arithmetic operations); for [`no-std`](#optional-features) builds, a number of additional dependencies are
|
||||
pulled in to provide for functionalities that used to be in `std`.
|
||||
|
||||
**Note:** Currently, the version is 0.13.0, so the language and API's may change before they stabilize.
|
||||
**Note:** Currently, the version is 0.14.1, so the language and API's may change before they stabilize.
|
||||
|
||||
Installation
|
||||
------------
|
||||
@ -38,7 +39,7 @@ Install the Rhai crate by adding this line to `dependencies`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
rhai = "0.13.0"
|
||||
rhai = "0.14.1"
|
||||
```
|
||||
|
||||
Use the latest released crate version on [`crates.io`](https::/crates.io/crates/rhai/):
|
||||
@ -69,6 +70,7 @@ Optional features
|
||||
| `no_object` | Disable support for custom types and objects. |
|
||||
| `no_float` | Disable floating-point numbers and math if not needed. |
|
||||
| `no_optimize` | Disable the script optimizer. |
|
||||
| `no_module` | Disable modules. |
|
||||
| `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. |
|
||||
| `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
|
||||
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
|
||||
@ -84,6 +86,7 @@ Excluding unneeded functionalities can result in smaller, faster builds as well
|
||||
[`no_function`]: #optional-features
|
||||
[`no_object`]: #optional-features
|
||||
[`no_optimize`]: #optional-features
|
||||
[`no_module`]: #optional-features
|
||||
[`only_i32`]: #optional-features
|
||||
[`only_i64`]: #optional-features
|
||||
[`no_std`]: #optional-features
|
||||
@ -375,7 +378,7 @@ engine.load_package(package.get()); // load the package manually
|
||||
The follow packages are available:
|
||||
|
||||
| Package | Description | In `CorePackage` | In `StandardPackage` |
|
||||
| ------------------------ | ----------------------------------------------- | :--------------: | :------------------: |
|
||||
| ---------------------- | ----------------------------------------------- | :--------------: | :------------------: |
|
||||
| `ArithmeticPackage` | Arithmetic operators (e.g. `+`, `-`, `*`, `/`) | Yes | Yes |
|
||||
| `BasicIteratorPackage` | Numeric ranges (e.g. `range(1, 10)`) | Yes | Yes |
|
||||
| `LogicPackage` | Logic and comparison operators (e.g. `==`, `>`) | Yes | Yes |
|
||||
@ -427,7 +430,7 @@ The following primitive types are supported natively:
|
||||
| **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` |
|
||||
| **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. |
|
||||
| **Unicode string** | `String` (_not_ `&str`) | `"string"` | `"hello"` etc. |
|
||||
| **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ? ? ? ]"` |
|
||||
| **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` |
|
||||
| **Object map** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `#{ "a": 1, "b": 2 }` |
|
||||
| **Timestamp** (implemented in the [`BasicTimePackage`](#packages)) | `std::time::Instant` | `"timestamp"` | _not supported_ |
|
||||
| **Dynamic value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ |
|
||||
@ -576,6 +579,7 @@ A number of traits, under the `rhai::` module namespace, provide additional func
|
||||
| `RegisterDynamicFn` | Trait for registering functions returning [`Dynamic`] | `register_dynamic_fn` |
|
||||
| `RegisterResultFn` | Trait for registering fallible functions returning `Result<`_T_`, Box<EvalAltResult>>` | `register_result_fn` |
|
||||
| `Func` | Trait for creating anonymous functions from script | `create_from_ast`, `create_from_script` |
|
||||
| `ModuleResolver` | Trait implemented by module resolution services | `resolve` |
|
||||
|
||||
Working with functions
|
||||
----------------------
|
||||
@ -923,8 +927,44 @@ let result = engine.eval::<i64>("let a = new_ts(); a.xyz = 42; a.xyz")?;
|
||||
println!("Answer: {}", result); // prints 42
|
||||
```
|
||||
|
||||
Needless to say, `register_type`, `register_type_with_name`, `register_get`, `register_set` and `register_get_set`
|
||||
are not available when the [`no_object`] feature is turned on.
|
||||
Indexers
|
||||
--------
|
||||
|
||||
Custom types can also expose an _indexer_ by registering an indexer function.
|
||||
A custom with an indexer function defined can use the bracket '`[]`' notation to get a property value
|
||||
(but not update it - indexers are read-only).
|
||||
|
||||
```rust
|
||||
#[derive(Clone)]
|
||||
struct TestStruct {
|
||||
fields: Vec<i64>
|
||||
}
|
||||
|
||||
impl TestStruct {
|
||||
fn get_field(&mut self, index: i64) -> i64 {
|
||||
self.fields[index as usize]
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
TestStruct { fields: vec![1, 2, 42, 4, 5] }
|
||||
}
|
||||
}
|
||||
|
||||
let engine = Engine::new();
|
||||
|
||||
engine.register_type::<TestStruct>();
|
||||
|
||||
engine.register_fn("new_ts", TestStruct::new);
|
||||
engine.register_indexer(TestStruct::get_field);
|
||||
|
||||
let result = engine.eval::<i64>("let a = new_ts(); a[2]")?;
|
||||
|
||||
println!("Answer: {}", result); // prints 42
|
||||
```
|
||||
|
||||
Needless to say, `register_type`, `register_type_with_name`, `register_get`, `register_set`, `register_get_set`
|
||||
and `register_indexer` are not available when the [`no_object`] feature is turned on.
|
||||
`register_indexer` is also not available when the [`no_index`] feature is turned on.
|
||||
|
||||
`Scope` - Initializing and maintaining state
|
||||
-------------------------------------------
|
||||
@ -1372,7 +1412,8 @@ y[2] == 3;
|
||||
y[3] == 4;
|
||||
|
||||
(1 in y) == true; // use 'in' to test if an item exists in the array
|
||||
(42 in y) == false;
|
||||
(42 in y) == false; // 'in' uses the '==' operator (which users can override)
|
||||
// to check if the target item exists in the array
|
||||
|
||||
y[1] = 42; // array elements can be reassigned
|
||||
|
||||
@ -1494,7 +1535,7 @@ y.a == 42;
|
||||
|
||||
y["baz!$@"] == 123.456; // access via index notation
|
||||
|
||||
"baz!$@" in y == true; // use 'in' to test if a property exists in the object map, prints true
|
||||
"baz!$@" in y == true; // use 'in' to test if a property exists in the object map
|
||||
("z" in y) == false;
|
||||
|
||||
ts.obj = y; // object maps can be assigned completely (by value copy)
|
||||
@ -1973,8 +2014,8 @@ When embedding Rhai into an application, it is usually necessary to trap `print`
|
||||
(for logging into a tracking log, for example).
|
||||
|
||||
```rust
|
||||
// Any function or closure that takes an &str argument can be used to override
|
||||
// print and debug
|
||||
// Any function or closure that takes an '&str' argument can be used to override
|
||||
// 'print' and 'debug'
|
||||
engine.on_print(|x| println!("hello: {}", x));
|
||||
engine.on_debug(|x| println!("DEBUG: {}", x));
|
||||
|
||||
@ -1997,6 +2038,98 @@ for entry in logbook.read().unwrap().iter() {
|
||||
}
|
||||
```
|
||||
|
||||
Using external modules
|
||||
----------------------
|
||||
|
||||
[module]: #using-external-modules
|
||||
[modules]: #using-external-modules
|
||||
|
||||
Rhai allows organizing code (functions and variables) into _modules_. A module is a single script file
|
||||
with `export` statements that _exports_ certain global variables and functions as contents of the module.
|
||||
|
||||
Everything exported as part of a module is constant and read-only.
|
||||
|
||||
### Importing modules
|
||||
|
||||
A module can be _imported_ via the `import` statement, and its members accessed via '`::`' similar to C++.
|
||||
|
||||
```rust
|
||||
import "crypto" as crypto; // import the script file 'crypto.rhai' as a module
|
||||
|
||||
crypto::encrypt(secret); // use functions defined under the module via '::'
|
||||
|
||||
print(crypto::status); // module variables are constants
|
||||
|
||||
crypto::hash::sha256(key); // sub-modules are also supported
|
||||
```
|
||||
|
||||
`import` statements are _scoped_, meaning that they are only accessible inside the scope that they're imported.
|
||||
|
||||
```rust
|
||||
let mod = "crypto";
|
||||
|
||||
if secured { // new block scope
|
||||
import mod as crypto; // import module (the path needs not be a constant string)
|
||||
|
||||
crypto::encrypt(key); // use a function in the module
|
||||
} // the module disappears at the end of the block scope
|
||||
|
||||
crypto::encrypt(others); // <- this causes a run-time error because the 'crypto' module
|
||||
// is no longer available!
|
||||
```
|
||||
|
||||
### Creating custom modules from Rust
|
||||
|
||||
To load a custom module into an [`Engine`], first create a `Module` type, add variables/functions into it,
|
||||
then finally push it into a custom [`Scope`]. This has the equivalent effect of putting an `import` statement
|
||||
at the beginning of any script run.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Scope, Module, i64};
|
||||
|
||||
let mut engine = Engine::new();
|
||||
let mut scope = Scope::new();
|
||||
|
||||
let mut module = Module::new(); // new module
|
||||
module.set_var("answer", 41_i64); // variable 'answer' under module
|
||||
module.set_fn_1("inc", |x: i64| Ok(x+1)); // use the 'set_fn_XXX' API to add functions
|
||||
|
||||
// Push the module into the custom scope under the name 'question'
|
||||
// This is equivalent to 'import "..." as question;'
|
||||
scope.push_module("question", module);
|
||||
|
||||
// Use module-qualified variables
|
||||
engine.eval_expression_with_scope::<i64>(&scope, "question::answer + 1")? == 42;
|
||||
|
||||
// Call module-qualified functions
|
||||
engine.eval_expression_with_scope::<i64>(&scope, "question::inc(question::answer)")? == 42;
|
||||
```
|
||||
|
||||
### Module resolvers
|
||||
|
||||
When encountering an `import` statement, Rhai attempts to _resolve_ the module based on the path string.
|
||||
_Module Resolvers_ are service types that implement the [`ModuleResolver`](#traits) trait.
|
||||
There are a number of standard resolvers built into Rhai, the default being the `FileModuleResolver`
|
||||
which simply loads a script file based on the path (with `.rhai` extension attached) and execute it to form a module.
|
||||
|
||||
Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace.
|
||||
|
||||
| Module Resolver | Description |
|
||||
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `FileModuleResolver` | The default module resolution service, not available under the [`no_std`] feature. Loads a script file (based off the current directory) with `.rhai` extension.<br/>The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function. |
|
||||
| `StaticModuleResolver` | Loads modules that are statically added. This can be used when the [`no_std`] feature is turned on. |
|
||||
|
||||
An [`Engine`]'s module resolver is set via a call to `set_module_resolver`:
|
||||
|
||||
```rust
|
||||
// Use the 'StaticModuleResolver'
|
||||
let resolver = rhai::module_resolvers::StaticModuleResolver::new();
|
||||
engine.set_module_resolver(Some(resolver));
|
||||
|
||||
// Effectively disable 'import' statements by setting module resolver to 'None'
|
||||
engine.set_module_resolver(None);
|
||||
```
|
||||
|
||||
Script optimization
|
||||
===================
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use rhai::{Dynamic, Engine, EvalAltResult, Scope, AST};
|
||||
use rhai::{Dynamic, Engine, EvalAltResult, Scope, AST, INT};
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
use rhai::OptimizationLevel;
|
||||
@ -68,9 +68,9 @@ fn main() {
|
||||
let mut scope = Scope::new();
|
||||
|
||||
let mut input = String::new();
|
||||
let mut main_ast = AST::new();
|
||||
let mut ast_u = AST::new();
|
||||
let mut ast = AST::new();
|
||||
let mut main_ast: AST = Default::default();
|
||||
let mut ast_u: AST = Default::default();
|
||||
let mut ast: AST = Default::default();
|
||||
|
||||
println!("Rhai REPL tool");
|
||||
println!("==============");
|
||||
@ -163,6 +163,7 @@ fn main() {
|
||||
}
|
||||
|
||||
// Throw away all the statements, leaving only the functions
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
main_ast.retain_functions();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
let x = [1, 2, 3];
|
||||
|
||||
print(x[1]); // prints 2
|
||||
print("x[1] should be 2:");
|
||||
print(x[1]);
|
||||
|
||||
x[1] = 5;
|
||||
|
||||
print(x[1]); // prints 5
|
||||
print("x[1] should be 5:");
|
||||
print(x[1]);
|
||||
|
@ -1,2 +1,4 @@
|
||||
print("x should be 78:");
|
||||
|
||||
let x = 78;
|
||||
print(x);
|
||||
|
@ -3,7 +3,8 @@
|
||||
let /* I am a spy in a variable declaration! */ x = 5;
|
||||
|
||||
/* I am a simple
|
||||
multi-line comment */
|
||||
multi-line
|
||||
comment */
|
||||
|
||||
/* look /* at /* that, /* multi-line */ comments */ can be */ nested */
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
// This script runs for-loops
|
||||
|
||||
let arr = [1,2,3,4];
|
||||
let arr = [1, 2, 3, 4];
|
||||
|
||||
for a in arr {
|
||||
for b in [10, 20] {
|
||||
|
@ -4,4 +4,6 @@ fn bob() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
print(bob()); // should print 3
|
||||
print("bob() should be 3:");
|
||||
|
||||
print(bob());
|
||||
|
@ -7,6 +7,10 @@ fn addme(a, b) {
|
||||
a + b; // notice that the last value is returned even if terminated by a semicolon
|
||||
}
|
||||
|
||||
print(addme(a, 4)); // should print 46
|
||||
print("addme(a, 4) should be 46:");
|
||||
|
||||
print(addme(a, 4));
|
||||
|
||||
print("a should still be 3:");
|
||||
|
||||
print(a); // should print 3 - 'a' is never changed
|
||||
|
@ -4,4 +4,6 @@ fn f(a, b, c, d, e, f) {
|
||||
a - b * c - d * e - f
|
||||
}
|
||||
|
||||
print(f(100, 5, 2, 9, 6, 32)); // should print 4
|
||||
print("f() call should be 4:");
|
||||
|
||||
print(f(100, 5, 2, 9, 6, 32));
|
||||
|
@ -3,12 +3,12 @@ let b = 123;
|
||||
let x = 999;
|
||||
|
||||
if a > b {
|
||||
print("a > b");
|
||||
print("Oops! a > b");
|
||||
} else if a < b {
|
||||
print("a < b");
|
||||
print("a < b, x should be 0");
|
||||
|
||||
let x = 0; // this 'x' shadows the global 'x'
|
||||
print(x); // should print 0
|
||||
} else {
|
||||
print("a == b");
|
||||
print("Oops! a == b");
|
||||
}
|
@ -1 +1,3 @@
|
||||
print(34 + 12); // should be 46
|
||||
print("The result should be 46:");
|
||||
|
||||
print(34 + 12);
|
||||
|
@ -1,2 +1,4 @@
|
||||
print("The result should be 182:");
|
||||
|
||||
let x = 12 + 34 * 5;
|
||||
print(x); // should be 182
|
||||
print(x);
|
||||
|
@ -1,2 +1,4 @@
|
||||
print("The result should be 230:");
|
||||
|
||||
let x = (12 + 34) * 5;
|
||||
print(x); // should be 230
|
||||
print(x);
|
||||
|
@ -13,18 +13,17 @@ prime_mask[1] = false;
|
||||
let total_primes_found = 0;
|
||||
|
||||
for p in range(2, MAX_NUMBER_TO_CHECK) {
|
||||
if prime_mask[p] {
|
||||
if !prime_mask[p] { continue; }
|
||||
|
||||
print(p);
|
||||
|
||||
total_primes_found += 1;
|
||||
let i = 2 * p;
|
||||
|
||||
while i < MAX_NUMBER_TO_CHECK {
|
||||
for i in range(2 * p, MAX_NUMBER_TO_CHECK, p) {
|
||||
prime_mask[i] = false;
|
||||
i += p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print("Total " + total_primes_found + " primes.");
|
||||
print("Total " + total_primes_found + " primes <= " + MAX_NUMBER_TO_CHECK);
|
||||
print("Run time = " + now.elapsed() + " seconds.");
|
||||
|
225
src/any.rs
225
src/any.rs
@ -1,11 +1,19 @@
|
||||
//! Helper module which defines the `Any` trait to to allow dynamic value handling.
|
||||
|
||||
use crate::engine::{Array, Map};
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
use crate::module::Module;
|
||||
|
||||
use crate::parser::INT;
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
use crate::parser::FLOAT;
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
use crate::engine::Array;
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
use crate::engine::Map;
|
||||
|
||||
use crate::stdlib::{
|
||||
any::{type_name, Any, TypeId},
|
||||
boxed::Box,
|
||||
@ -119,18 +127,6 @@ impl dyn Variant {
|
||||
pub fn is<T: Any>(&self) -> bool {
|
||||
TypeId::of::<T>() == self.type_id()
|
||||
}
|
||||
|
||||
/// Get a reference of a specific type to the `Variant`.
|
||||
/// Returns `None` if the cast fails.
|
||||
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
|
||||
Any::downcast_ref::<T>(self.as_any())
|
||||
}
|
||||
|
||||
/// Get a mutable reference of a specific type to the `Variant`.
|
||||
/// Returns `None` if the cast fails.
|
||||
pub fn downcast_mut<T: Any>(&mut self) -> Option<&mut T> {
|
||||
Any::downcast_mut::<T>(self.as_mut_any())
|
||||
}
|
||||
}
|
||||
|
||||
/// A dynamic type containing any value.
|
||||
@ -145,8 +141,12 @@ pub enum Union {
|
||||
Int(INT),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Float(FLOAT),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Array(Box<Array>),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Map(Box<Map>),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Module(Box<Module>),
|
||||
Variant(Box<Box<dyn Variant>>),
|
||||
}
|
||||
|
||||
@ -175,8 +175,12 @@ impl Dynamic {
|
||||
Union::Int(_) => TypeId::of::<INT>(),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Union::Float(_) => TypeId::of::<FLOAT>(),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(_) => TypeId::of::<Array>(),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(_) => TypeId::of::<Map>(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Union::Module(_) => TypeId::of::<Module>(),
|
||||
Union::Variant(value) => (***value).type_id(),
|
||||
}
|
||||
}
|
||||
@ -191,8 +195,12 @@ impl Dynamic {
|
||||
Union::Int(_) => type_name::<INT>(),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Union::Float(_) => type_name::<FLOAT>(),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(_) => "array",
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(_) => "map",
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Union::Module(_) => "sub-scope",
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
Union::Variant(value) if value.is::<Instant>() => "timestamp",
|
||||
@ -211,8 +219,15 @@ impl fmt::Display for Dynamic {
|
||||
Union::Int(value) => write!(f, "{}", value),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Union::Float(value) => write!(f, "{}", value),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(value) => write!(f, "{:?}", value),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(value) => write!(f, "#{:?}", value),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Union::Module(value) => write!(f, "{:?}", value),
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
|
||||
Union::Variant(_) => write!(f, "?"),
|
||||
}
|
||||
}
|
||||
@ -228,8 +243,15 @@ impl fmt::Debug for Dynamic {
|
||||
Union::Int(value) => write!(f, "{:?}", value),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Union::Float(value) => write!(f, "{:?}", value),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(value) => write!(f, "{:?}", value),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(value) => write!(f, "#{:?}", value),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Union::Module(value) => write!(f, "{:?}", value),
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
|
||||
Union::Variant(_) => write!(f, "<dynamic>"),
|
||||
}
|
||||
}
|
||||
@ -237,17 +259,21 @@ impl fmt::Debug for Dynamic {
|
||||
|
||||
impl Clone for Dynamic {
|
||||
fn clone(&self) -> Self {
|
||||
match &self.0 {
|
||||
Union::Unit(value) => Self(Union::Unit(value.clone())),
|
||||
Union::Bool(value) => Self(Union::Bool(value.clone())),
|
||||
Union::Str(value) => Self(Union::Str(value.clone())),
|
||||
Union::Char(value) => Self(Union::Char(value.clone())),
|
||||
Union::Int(value) => Self(Union::Int(value.clone())),
|
||||
match self.0 {
|
||||
Union::Unit(value) => Self(Union::Unit(value)),
|
||||
Union::Bool(value) => Self(Union::Bool(value)),
|
||||
Union::Str(ref value) => Self(Union::Str(value.clone())),
|
||||
Union::Char(value) => Self(Union::Char(value)),
|
||||
Union::Int(value) => Self(Union::Int(value)),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Union::Float(value) => Self(Union::Float(value.clone())),
|
||||
Union::Array(value) => Self(Union::Array(value.clone())),
|
||||
Union::Map(value) => Self(Union::Map(value.clone())),
|
||||
Union::Variant(value) => (***value).clone_into_dynamic(),
|
||||
Union::Float(value) => Self(Union::Float(value)),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(ref value) => Self(Union::Array(value.clone())),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(ref value) => Self(Union::Map(value.clone())),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Union::Module(ref value) => Self(Union::Module(value.clone())),
|
||||
Union::Variant(ref value) => (***value).clone_into_dynamic(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -259,13 +285,13 @@ impl Default for Dynamic {
|
||||
}
|
||||
|
||||
/// Cast a Boxed type into another type.
|
||||
fn cast_box<X: Variant, T: Variant>(item: Box<X>) -> Result<T, Box<X>> {
|
||||
fn cast_box<X: Variant, T: Variant>(item: Box<X>) -> Result<Box<T>, Box<X>> {
|
||||
// Only allow casting to the exact same type
|
||||
if TypeId::of::<X>() == TypeId::of::<T>() {
|
||||
// SAFETY: just checked whether we are pointing to the correct type
|
||||
unsafe {
|
||||
let raw: *mut dyn Any = Box::into_raw(item as Box<dyn Any>);
|
||||
Ok(*Box::from_raw(raw as *mut T))
|
||||
Ok(Box::from_raw(raw as *mut T))
|
||||
}
|
||||
} else {
|
||||
// Return the consumed item for chaining.
|
||||
@ -300,7 +326,7 @@ impl Dynamic {
|
||||
/// assert_eq!(new_result.to_string(), "hello");
|
||||
/// ```
|
||||
pub fn from<T: Variant + Clone>(value: T) -> Self {
|
||||
let dyn_value = &value as &dyn Variant;
|
||||
let dyn_value = &value as &dyn Any;
|
||||
|
||||
if let Some(result) = dyn_value.downcast_ref::<()>().cloned().map(Union::Unit) {
|
||||
return Self(result);
|
||||
@ -319,31 +345,33 @@ impl Dynamic {
|
||||
}
|
||||
}
|
||||
|
||||
let var = Box::new(value);
|
||||
let mut var = Box::new(value);
|
||||
|
||||
Self(
|
||||
cast_box::<_, Dynamic>(var)
|
||||
.map(|x| x.0)
|
||||
.or_else(|var| {
|
||||
cast_box::<_, String>(var)
|
||||
.map(Box::new)
|
||||
.map(Union::Str)
|
||||
.or_else(|var| {
|
||||
cast_box::<_, Array>(var)
|
||||
.map(Box::new)
|
||||
.map(Union::Array)
|
||||
.or_else(|var| {
|
||||
cast_box::<_, Map>(var)
|
||||
.map(Box::new)
|
||||
.map(Union::Map)
|
||||
.or_else(|var| -> Result<Union, ()> {
|
||||
Ok(Union::Variant(Box::new(var as Box<dyn Variant>)))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
.unwrap(),
|
||||
)
|
||||
var = match cast_box::<_, Dynamic>(var) {
|
||||
Ok(d) => return *d,
|
||||
Err(var) => var,
|
||||
};
|
||||
var = match cast_box::<_, String>(var) {
|
||||
Ok(s) => return Self(Union::Str(s)),
|
||||
Err(var) => var,
|
||||
};
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
{
|
||||
var = match cast_box::<_, Array>(var) {
|
||||
Ok(array) => return Self(Union::Array(array)),
|
||||
Err(var) => var,
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
{
|
||||
var = match cast_box::<_, Map>(var) {
|
||||
Ok(map) => return Self(Union::Map(map)),
|
||||
Err(var) => var,
|
||||
}
|
||||
}
|
||||
|
||||
Self(Union::Variant(Box::new(var)))
|
||||
}
|
||||
|
||||
/// Get a copy of the `Dynamic` value as a specific type.
|
||||
@ -362,20 +390,24 @@ impl Dynamic {
|
||||
/// ```
|
||||
pub fn try_cast<T: Variant + Clone>(self) -> Option<T> {
|
||||
if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
|
||||
return cast_box::<_, T>(Box::new(self)).ok();
|
||||
return cast_box::<_, T>(Box::new(self)).ok().map(|v| *v);
|
||||
}
|
||||
|
||||
match self.0 {
|
||||
Union::Unit(ref value) => (value as &dyn Variant).downcast_ref::<T>().cloned(),
|
||||
Union::Bool(ref value) => (value as &dyn Variant).downcast_ref::<T>().cloned(),
|
||||
Union::Str(value) => cast_box::<_, T>(value).ok(),
|
||||
Union::Char(ref value) => (value as &dyn Variant).downcast_ref::<T>().cloned(),
|
||||
Union::Int(ref value) => (value as &dyn Variant).downcast_ref::<T>().cloned(),
|
||||
Union::Unit(ref value) => (value as &dyn Any).downcast_ref::<T>().cloned(),
|
||||
Union::Bool(ref value) => (value as &dyn Any).downcast_ref::<T>().cloned(),
|
||||
Union::Str(value) => cast_box::<_, T>(value).ok().map(|v| *v),
|
||||
Union::Char(ref value) => (value as &dyn Any).downcast_ref::<T>().cloned(),
|
||||
Union::Int(ref value) => (value as &dyn Any).downcast_ref::<T>().cloned(),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Union::Float(ref value) => (value as &dyn Variant).downcast_ref::<T>().cloned(),
|
||||
Union::Array(value) => cast_box::<_, T>(value).ok(),
|
||||
Union::Map(value) => cast_box::<_, T>(value).ok(),
|
||||
Union::Variant(value) => value.as_ref().as_ref().downcast_ref::<T>().cloned(),
|
||||
Union::Float(ref value) => (value as &dyn Any).downcast_ref::<T>().cloned(),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(value) => cast_box::<_, T>(value).ok().map(|v| *v),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(value) => cast_box::<_, T>(value).ok().map(|v| *v),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Union::Module(value) => cast_box::<_, T>(value).ok().map(|v| *v),
|
||||
Union::Variant(value) => value.as_any().downcast_ref::<T>().cloned(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -396,7 +428,28 @@ impl Dynamic {
|
||||
/// assert_eq!(x.cast::<u32>(), 42);
|
||||
/// ```
|
||||
pub fn cast<T: Variant + Clone>(self) -> T {
|
||||
self.try_cast::<T>().unwrap()
|
||||
//self.try_cast::<T>().unwrap()
|
||||
|
||||
if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
|
||||
return *cast_box::<_, T>(Box::new(self)).unwrap();
|
||||
}
|
||||
|
||||
match self.0 {
|
||||
Union::Unit(ref value) => (value as &dyn Any).downcast_ref::<T>().unwrap().clone(),
|
||||
Union::Bool(ref value) => (value as &dyn Any).downcast_ref::<T>().unwrap().clone(),
|
||||
Union::Str(value) => *cast_box::<_, T>(value).unwrap(),
|
||||
Union::Char(ref value) => (value as &dyn Any).downcast_ref::<T>().unwrap().clone(),
|
||||
Union::Int(ref value) => (value as &dyn Any).downcast_ref::<T>().unwrap().clone(),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Union::Float(ref value) => (value as &dyn Any).downcast_ref::<T>().unwrap().clone(),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(value) => *cast_box::<_, T>(value).unwrap(),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(value) => *cast_box::<_, T>(value).unwrap(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Union::Module(value) => *cast_box::<_, T>(value).unwrap(),
|
||||
Union::Variant(value) => value.as_any().downcast_ref::<T>().unwrap().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference of a specific type to the `Dynamic`.
|
||||
@ -404,20 +457,24 @@ impl Dynamic {
|
||||
/// Returns `None` if the cast fails.
|
||||
pub fn downcast_ref<T: Variant + Clone>(&self) -> Option<&T> {
|
||||
if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
|
||||
return (self as &dyn Variant).downcast_ref::<T>();
|
||||
return (self as &dyn Any).downcast_ref::<T>();
|
||||
}
|
||||
|
||||
match &self.0 {
|
||||
Union::Unit(value) => (value as &dyn Variant).downcast_ref::<T>(),
|
||||
Union::Bool(value) => (value as &dyn Variant).downcast_ref::<T>(),
|
||||
Union::Str(value) => (value.as_ref() as &dyn Variant).downcast_ref::<T>(),
|
||||
Union::Char(value) => (value as &dyn Variant).downcast_ref::<T>(),
|
||||
Union::Int(value) => (value as &dyn Variant).downcast_ref::<T>(),
|
||||
Union::Unit(value) => (value as &dyn Any).downcast_ref::<T>(),
|
||||
Union::Bool(value) => (value as &dyn Any).downcast_ref::<T>(),
|
||||
Union::Str(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(),
|
||||
Union::Char(value) => (value as &dyn Any).downcast_ref::<T>(),
|
||||
Union::Int(value) => (value as &dyn Any).downcast_ref::<T>(),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Union::Float(value) => (value as &dyn Variant).downcast_ref::<T>(),
|
||||
Union::Array(value) => (value.as_ref() as &dyn Variant).downcast_ref::<T>(),
|
||||
Union::Map(value) => (value.as_ref() as &dyn Variant).downcast_ref::<T>(),
|
||||
Union::Variant(value) => value.as_ref().as_ref().downcast_ref::<T>(),
|
||||
Union::Float(value) => (value as &dyn Any).downcast_ref::<T>(),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Union::Module(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(),
|
||||
Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::<T>(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -426,20 +483,24 @@ impl Dynamic {
|
||||
/// Returns `None` if the cast fails.
|
||||
pub fn downcast_mut<T: Variant + Clone>(&mut self) -> Option<&mut T> {
|
||||
if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
|
||||
return (self as &mut dyn Variant).downcast_mut::<T>();
|
||||
return (self as &mut dyn Any).downcast_mut::<T>();
|
||||
}
|
||||
|
||||
match &mut self.0 {
|
||||
Union::Unit(value) => (value as &mut dyn Variant).downcast_mut::<T>(),
|
||||
Union::Bool(value) => (value as &mut dyn Variant).downcast_mut::<T>(),
|
||||
Union::Str(value) => (value.as_mut() as &mut dyn Variant).downcast_mut::<T>(),
|
||||
Union::Char(value) => (value as &mut dyn Variant).downcast_mut::<T>(),
|
||||
Union::Int(value) => (value as &mut dyn Variant).downcast_mut::<T>(),
|
||||
Union::Unit(value) => (value as &mut dyn Any).downcast_mut::<T>(),
|
||||
Union::Bool(value) => (value as &mut dyn Any).downcast_mut::<T>(),
|
||||
Union::Str(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(),
|
||||
Union::Char(value) => (value as &mut dyn Any).downcast_mut::<T>(),
|
||||
Union::Int(value) => (value as &mut dyn Any).downcast_mut::<T>(),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Union::Float(value) => (value as &mut dyn Variant).downcast_mut::<T>(),
|
||||
Union::Array(value) => (value.as_mut() as &mut dyn Variant).downcast_mut::<T>(),
|
||||
Union::Map(value) => (value.as_mut() as &mut dyn Variant).downcast_mut::<T>(),
|
||||
Union::Variant(value) => value.as_mut().as_mut().downcast_mut::<T>(),
|
||||
Union::Float(value) => (value as &mut dyn Any).downcast_mut::<T>(),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Union::Module(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(),
|
||||
Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::<T>(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -520,6 +581,7 @@ impl From<String> for Dynamic {
|
||||
Self(Union::Str(Box::new(value)))
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
impl<T: Variant + Clone> From<Vec<T>> for Dynamic {
|
||||
fn from(value: Vec<T>) -> Self {
|
||||
Self(Union::Array(Box::new(
|
||||
@ -527,6 +589,7 @@ impl<T: Variant + Clone> From<Vec<T>> for Dynamic {
|
||||
)))
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
impl<T: Variant + Clone> From<HashMap<String, T>> for Dynamic {
|
||||
fn from(value: HashMap<String, T>) -> Self {
|
||||
Self(Union::Map(Box::new(
|
||||
|
100
src/api.rs
100
src/api.rs
@ -1,7 +1,7 @@
|
||||
//! Module that defines the extern API of `Engine`.
|
||||
|
||||
use crate::any::{Dynamic, Variant};
|
||||
use crate::engine::{make_getter, make_setter, Engine, Map, State};
|
||||
use crate::engine::{make_getter, make_setter, Engine, State, FUNC_INDEXER};
|
||||
use crate::error::ParseError;
|
||||
use crate::fn_call::FuncArgs;
|
||||
use crate::fn_register::RegisterFn;
|
||||
@ -11,10 +11,14 @@ use crate::result::EvalAltResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::token::{lex, Position};
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
use crate::engine::Map;
|
||||
|
||||
use crate::stdlib::{
|
||||
any::{type_name, TypeId},
|
||||
boxed::Box,
|
||||
collections::HashMap,
|
||||
mem,
|
||||
string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
@ -42,6 +46,16 @@ pub trait ObjectSetCallback<T, U>: Fn(&mut T, U) + 'static {}
|
||||
#[cfg(not(feature = "sync"))]
|
||||
impl<F: Fn(&mut T, U) + 'static, T, U> ObjectSetCallback<T, U> for F {}
|
||||
|
||||
#[cfg(feature = "sync")]
|
||||
pub trait ObjectIndexerCallback<T, X, U>: Fn(&mut T, X) -> U + Send + Sync + 'static {}
|
||||
#[cfg(feature = "sync")]
|
||||
impl<F: Fn(&mut T, X) -> U + Send + Sync + 'static, T, X, U> ObjectIndexerCallback<T, X, U> for F {}
|
||||
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub trait ObjectIndexerCallback<T, X, U>: Fn(&mut T, X) -> U + 'static {}
|
||||
#[cfg(not(feature = "sync"))]
|
||||
impl<F: Fn(&mut T, X) -> U + 'static, T, X, U> ObjectIndexerCallback<T, X, U> for F {}
|
||||
|
||||
#[cfg(feature = "sync")]
|
||||
pub trait IteratorCallback:
|
||||
Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + Send + Sync + 'static
|
||||
@ -299,6 +313,54 @@ impl Engine {
|
||||
self.register_set(name, set_fn);
|
||||
}
|
||||
|
||||
/// Register an indexer function for a registered type with the `Engine`.
|
||||
///
|
||||
/// The function signature must start with `&mut self` and not `&self`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// #[derive(Clone)]
|
||||
/// struct TestStruct {
|
||||
/// fields: Vec<i64>
|
||||
/// }
|
||||
///
|
||||
/// impl TestStruct {
|
||||
/// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } }
|
||||
///
|
||||
/// // Even a getter must start with `&mut self` and not `&self`.
|
||||
/// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] }
|
||||
/// }
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// use rhai::{Engine, RegisterFn};
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Register the custom type.
|
||||
/// engine.register_type::<TestStruct>();
|
||||
///
|
||||
/// engine.register_fn("new_ts", TestStruct::new);
|
||||
///
|
||||
/// // Register an indexer.
|
||||
/// engine.register_indexer(TestStruct::get_field);
|
||||
///
|
||||
/// assert_eq!(engine.eval::<i64>("let a = new_ts(); a[2]")?, 3);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
pub fn register_indexer<T, X, U, F>(&mut self, callback: F)
|
||||
where
|
||||
T: Variant + Clone,
|
||||
U: Variant + Clone,
|
||||
X: Variant + Clone,
|
||||
F: ObjectIndexerCallback<T, X, U>,
|
||||
{
|
||||
self.register_fn(FUNC_INDEXER, callback);
|
||||
}
|
||||
|
||||
/// Compile a string into an `AST`, which can be used later for evaluation.
|
||||
///
|
||||
/// # Example
|
||||
@ -378,13 +440,23 @@ impl Engine {
|
||||
/// Read the contents of a file into a string.
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
fn read_file(path: PathBuf) -> Result<String, Box<EvalAltResult>> {
|
||||
let mut f = File::open(path.clone())
|
||||
.map_err(|err| Box::new(EvalAltResult::ErrorReadingScriptFile(path.clone(), err)))?;
|
||||
let mut f = File::open(path.clone()).map_err(|err| {
|
||||
Box::new(EvalAltResult::ErrorReadingScriptFile(
|
||||
path.clone(),
|
||||
Position::none(),
|
||||
err,
|
||||
))
|
||||
})?;
|
||||
|
||||
let mut contents = String::new();
|
||||
|
||||
f.read_to_string(&mut contents)
|
||||
.map_err(|err| Box::new(EvalAltResult::ErrorReadingScriptFile(path.clone(), err)))?;
|
||||
f.read_to_string(&mut contents).map_err(|err| {
|
||||
Box::new(EvalAltResult::ErrorReadingScriptFile(
|
||||
path.clone(),
|
||||
Position::none(),
|
||||
err,
|
||||
))
|
||||
})?;
|
||||
|
||||
Ok(contents)
|
||||
}
|
||||
@ -797,10 +869,10 @@ impl Engine {
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
let mut state = State::new();
|
||||
|
||||
ast.0
|
||||
ast.statements()
|
||||
.iter()
|
||||
.try_fold(().into(), |_, stmt| {
|
||||
self.eval_stmt(scope, &mut state, ast.1.as_ref(), stmt, 0)
|
||||
self.eval_stmt(scope, &mut state, ast.fn_lib(), stmt, 0)
|
||||
})
|
||||
.or_else(|err| match *err {
|
||||
EvalAltResult::Return(out, _) => Ok(out),
|
||||
@ -862,10 +934,10 @@ impl Engine {
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
let mut state = State::new();
|
||||
|
||||
ast.0
|
||||
ast.statements()
|
||||
.iter()
|
||||
.try_fold(().into(), |_, stmt| {
|
||||
self.eval_stmt(scope, &mut state, ast.1.as_ref(), stmt, 0)
|
||||
self.eval_stmt(scope, &mut state, ast.fn_lib(), stmt, 0)
|
||||
})
|
||||
.map_or_else(
|
||||
|err| match *err {
|
||||
@ -921,7 +993,7 @@ impl Engine {
|
||||
) -> Result<T, Box<EvalAltResult>> {
|
||||
let mut arg_values = args.into_vec();
|
||||
let mut args: Vec<_> = arg_values.iter_mut().collect();
|
||||
let fn_lib = ast.1.as_ref();
|
||||
let fn_lib = ast.fn_lib();
|
||||
let pos = Position::none();
|
||||
|
||||
let fn_def = fn_lib
|
||||
@ -955,15 +1027,17 @@ impl Engine {
|
||||
pub fn optimize_ast(
|
||||
&self,
|
||||
scope: &Scope,
|
||||
ast: AST,
|
||||
mut ast: AST,
|
||||
optimization_level: OptimizationLevel,
|
||||
) -> AST {
|
||||
let fn_lib = ast
|
||||
.1
|
||||
.fn_lib()
|
||||
.iter()
|
||||
.map(|(_, fn_def)| fn_def.as_ref().clone())
|
||||
.collect();
|
||||
optimize_into_ast(self, scope, ast.0, fn_lib, optimization_level)
|
||||
|
||||
let stmt = mem::take(ast.statements_mut());
|
||||
optimize_into_ast(self, scope, stmt, fn_lib, optimization_level)
|
||||
}
|
||||
|
||||
/// Override default action of `print` (print to stdout using `println!`)
|
||||
|
422
src/engine.rs
422
src/engine.rs
@ -5,19 +5,23 @@ use crate::calc_fn_hash;
|
||||
use crate::error::ParseErrorType;
|
||||
use crate::optimize::OptimizationLevel;
|
||||
use crate::packages::{CorePackage, Package, PackageLibrary, StandardPackage};
|
||||
use crate::parser::{Expr, FnDef, ReturnType, Stmt};
|
||||
use crate::parser::{Expr, FnDef, ModuleRef, ReturnType, Stmt, AST};
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
||||
use crate::token::Position;
|
||||
use crate::utils::{calc_fn_def, StaticVec};
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
use crate::module::{resolvers, Module, ModuleResolver};
|
||||
|
||||
use crate::stdlib::{
|
||||
any::TypeId,
|
||||
boxed::Box,
|
||||
collections::HashMap,
|
||||
format,
|
||||
hash::{Hash, Hasher},
|
||||
iter::once,
|
||||
mem,
|
||||
num::NonZeroUsize,
|
||||
ops::{Deref, DerefMut},
|
||||
rc::Rc,
|
||||
string::{String, ToString},
|
||||
@ -25,20 +29,16 @@ use crate::stdlib::{
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
use crate::stdlib::collections::hash_map::DefaultHasher;
|
||||
|
||||
#[cfg(feature = "no_std")]
|
||||
use ahash::AHasher;
|
||||
|
||||
/// An dynamic array of `Dynamic` values.
|
||||
///
|
||||
/// Not available under the `no_index` feature.
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
pub type Array = Vec<Dynamic>;
|
||||
|
||||
/// An dynamic hash map of `Dynamic` values with `String` keys.
|
||||
///
|
||||
/// Not available under the `no_object` feature.
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
pub type Map = HashMap<String, Dynamic>;
|
||||
|
||||
pub type FnCallArgs<'a> = [&'a mut Dynamic];
|
||||
@ -74,6 +74,7 @@ pub const KEYWORD_EVAL: &str = "eval";
|
||||
pub const FUNC_TO_STRING: &str = "to_string";
|
||||
pub const FUNC_GETTER: &str = "get$";
|
||||
pub const FUNC_SETTER: &str = "set$";
|
||||
pub const FUNC_INDEXER: &str = "$index$";
|
||||
|
||||
/// A type that encapsulates a mutation target for an expression with side effects.
|
||||
enum Target<'a> {
|
||||
@ -120,7 +121,7 @@ impl Target<'_> {
|
||||
chars.iter().for_each(|&ch| s.push(ch));
|
||||
}
|
||||
}
|
||||
_ => panic!("should be String"),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
}
|
||||
|
||||
@ -139,62 +140,8 @@ impl<T: Into<Dynamic>> From<T> for Target<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A type to hold a number of `Dynamic` values in static storage for speed,
|
||||
/// and any spill-overs in a `Vec`.
|
||||
struct StaticVec<T: Default> {
|
||||
/// Total number of values held.
|
||||
len: usize,
|
||||
/// Static storage. 4 slots should be enough for most cases - i.e. four levels of indirection.
|
||||
list: [T; 4],
|
||||
/// Dynamic storage. For spill-overs.
|
||||
more: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T: Default> StaticVec<T> {
|
||||
/// Create a new `StaticVec`.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
len: 0,
|
||||
list: [
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
],
|
||||
more: Vec::new(),
|
||||
}
|
||||
}
|
||||
/// Push a new value to the end of this `StaticVec`.
|
||||
pub fn push<X: Into<T>>(&mut self, value: X) {
|
||||
if self.len >= self.list.len() {
|
||||
self.more.push(value.into());
|
||||
} else {
|
||||
self.list[self.len] = value.into();
|
||||
}
|
||||
self.len += 1;
|
||||
}
|
||||
/// Pop a value from the end of this `StaticVec`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the `StaticVec` is empty.
|
||||
pub fn pop(&mut self) -> T {
|
||||
let result = if self.len <= 0 {
|
||||
panic!("nothing to pop!")
|
||||
} else if self.len <= self.list.len() {
|
||||
mem::replace(self.list.get_mut(self.len - 1).unwrap(), Default::default())
|
||||
} else {
|
||||
self.more.pop().unwrap()
|
||||
};
|
||||
|
||||
self.len -= 1;
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that holds all the current states of the Engine.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct State {
|
||||
/// Normally, access to variables are parsed with a relative offset into the scope to avoid a lookup.
|
||||
/// In some situation, e.g. after running an `eval` statement, subsequent offsets may become mis-aligned.
|
||||
@ -217,7 +164,7 @@ impl State {
|
||||
/// and number of parameters are considered equivalent.
|
||||
///
|
||||
/// The key of the `HashMap` is a `u64` hash calculated by the function `calc_fn_def`.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct FunctionsLib(
|
||||
#[cfg(feature = "sync")] HashMap<u64, Arc<FnDef>>,
|
||||
#[cfg(not(feature = "sync"))] HashMap<u64, Rc<FnDef>>,
|
||||
@ -226,7 +173,7 @@ pub struct FunctionsLib(
|
||||
impl FunctionsLib {
|
||||
/// Create a new `FunctionsLib`.
|
||||
pub fn new() -> Self {
|
||||
FunctionsLib(HashMap::new())
|
||||
Default::default()
|
||||
}
|
||||
/// Create a new `FunctionsLib` from a collection of `FnDef`.
|
||||
pub fn from_vec(vec: Vec<FnDef>) -> Self {
|
||||
@ -317,6 +264,11 @@ pub struct Engine {
|
||||
|
||||
/// A hashmap containing all iterators known to the engine.
|
||||
pub(crate) type_iterators: HashMap<TypeId, Box<IteratorFn>>,
|
||||
|
||||
/// A module resolution service.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub(crate) module_resolver: Option<Box<dyn ModuleResolver>>,
|
||||
|
||||
/// A hashmap mapping type names to pretty-print names.
|
||||
pub(crate) type_names: HashMap<String, String>,
|
||||
|
||||
@ -347,10 +299,18 @@ impl Default for Engine {
|
||||
fn default() -> Self {
|
||||
// Create the new scripting Engine
|
||||
let mut engine = Self {
|
||||
packages: Vec::new(),
|
||||
packages: Default::default(),
|
||||
functions: HashMap::with_capacity(FUNCTIONS_COUNT),
|
||||
type_iterators: HashMap::new(),
|
||||
type_names: HashMap::new(),
|
||||
type_iterators: Default::default(),
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
module_resolver: Some(Box::new(resolvers::FileModuleResolver::new())),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(feature = "no_std")]
|
||||
module_resolver: None,
|
||||
|
||||
type_names: Default::default(),
|
||||
|
||||
// default print/debug implementations
|
||||
print: Box::new(default_print),
|
||||
@ -423,48 +383,53 @@ fn extract_prop_from_setter(fn_name: &str) -> Option<&str> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate a `u64` hash key from a function name and parameter types.
|
||||
///
|
||||
/// Parameter types are passed in via `TypeId` values from an iterator
|
||||
/// which can come from any source.
|
||||
pub fn calc_fn_spec(fn_name: &str, params: impl Iterator<Item = TypeId>) -> u64 {
|
||||
#[cfg(feature = "no_std")]
|
||||
let mut s: AHasher = Default::default();
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
let mut s = DefaultHasher::new();
|
||||
|
||||
s.write(fn_name.as_bytes());
|
||||
params.for_each(|t| t.hash(&mut s));
|
||||
s.finish()
|
||||
}
|
||||
|
||||
/// Calculate a `u64` hash key from a function name and number of parameters (without regard to types).
|
||||
pub(crate) fn calc_fn_def(fn_name: &str, params: usize) -> u64 {
|
||||
#[cfg(feature = "no_std")]
|
||||
let mut s: AHasher = Default::default();
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
let mut s = DefaultHasher::new();
|
||||
|
||||
s.write(fn_name.as_bytes());
|
||||
s.write_usize(params);
|
||||
s.finish()
|
||||
}
|
||||
|
||||
/// Print/debug to stdout
|
||||
fn default_print(s: &str) {
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
println!("{}", s);
|
||||
}
|
||||
|
||||
/// Search for a variable within the scope, returning its value and index inside the Scope
|
||||
/// Search for a variable within the scope
|
||||
fn search_scope<'a>(
|
||||
scope: &'a mut Scope,
|
||||
name: &str,
|
||||
begin: Position,
|
||||
modules: &ModuleRef,
|
||||
index: Option<NonZeroUsize>,
|
||||
pos: Position,
|
||||
) -> Result<(&'a mut Dynamic, ScopeEntryType), Box<EvalAltResult>> {
|
||||
let (index, _) = scope
|
||||
.get(name)
|
||||
.ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), begin)))?;
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
{
|
||||
if let Some(modules) = modules {
|
||||
let (id, root_pos) = modules.get(0); // First module
|
||||
|
||||
let module = if let Some(index) = index {
|
||||
scope
|
||||
.get_mut(scope.len() - index.get())
|
||||
.0
|
||||
.downcast_mut::<Module>()
|
||||
.unwrap()
|
||||
} else {
|
||||
scope.find_module(id).ok_or_else(|| {
|
||||
Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos))
|
||||
})?
|
||||
};
|
||||
|
||||
return Ok((
|
||||
module.get_qualified_var_mut(name, modules.as_ref(), pos)?,
|
||||
// Module variables are constant
|
||||
ScopeEntryType::Constant,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let index = if let Some(index) = index {
|
||||
scope.len() - index.get()
|
||||
} else {
|
||||
scope
|
||||
.get_index(name)
|
||||
.ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))?
|
||||
.0
|
||||
};
|
||||
|
||||
Ok(scope.get_mut(index))
|
||||
}
|
||||
@ -479,10 +444,14 @@ impl Engine {
|
||||
/// Use the `load_package` method to load packages of functions.
|
||||
pub fn new_raw() -> Self {
|
||||
Self {
|
||||
packages: Vec::new(),
|
||||
packages: Default::default(),
|
||||
functions: HashMap::with_capacity(FUNCTIONS_COUNT / 2),
|
||||
type_iterators: HashMap::new(),
|
||||
type_names: HashMap::new(),
|
||||
type_iterators: Default::default(),
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
module_resolver: None,
|
||||
|
||||
type_names: Default::default(),
|
||||
print: Box::new(|_| {}),
|
||||
debug: Box::new(|_| {}),
|
||||
|
||||
@ -510,7 +479,7 @@ impl Engine {
|
||||
self.packages.insert(0, package);
|
||||
}
|
||||
|
||||
/// Control whether and how the `Engine` will optimize an AST after compilation
|
||||
/// Control whether and how the `Engine` will optimize an AST after compilation.
|
||||
///
|
||||
/// Not available under the `no_optimize` feature.
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
@ -524,7 +493,15 @@ impl Engine {
|
||||
self.max_call_stack_depth = levels
|
||||
}
|
||||
|
||||
/// Universal method for calling functions either registered with the `Engine` or written in Rhai
|
||||
/// Set the module resolution service used by the `Engine`.
|
||||
///
|
||||
/// Not available under the `no_module` feature.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub fn set_module_resolver(&mut self, resolver: Option<impl ModuleResolver + 'static>) {
|
||||
self.module_resolver = resolver.map(|f| Box::new(f) as Box<dyn ModuleResolver>);
|
||||
}
|
||||
|
||||
/// Universal method for calling functions either registered with the `Engine` or written in Rhai.
|
||||
pub(crate) fn call_fn_raw(
|
||||
&self,
|
||||
scope: Option<&mut Scope>,
|
||||
@ -577,6 +554,11 @@ impl Engine {
|
||||
});
|
||||
}
|
||||
|
||||
// Return default value (if any)
|
||||
if let Some(val) = def_val {
|
||||
return Ok(val.clone());
|
||||
}
|
||||
|
||||
// Getter function not found?
|
||||
if let Some(prop) = extract_prop_from_getter(fn_name) {
|
||||
return Err(Box::new(EvalAltResult::ErrorDotExpr(
|
||||
@ -593,17 +575,20 @@ impl Engine {
|
||||
)));
|
||||
}
|
||||
|
||||
// Return default value (if any)
|
||||
if let Some(val) = def_val {
|
||||
return Ok(val.clone());
|
||||
}
|
||||
|
||||
// Raise error
|
||||
let types_list: Vec<_> = args
|
||||
.iter()
|
||||
.map(|name| self.map_type_name(name.type_name()))
|
||||
.collect();
|
||||
|
||||
// Getter function not found?
|
||||
if fn_name == FUNC_INDEXER {
|
||||
return Err(Box::new(EvalAltResult::ErrorFunctionNotFound(
|
||||
format!("[]({})", types_list.join(", ")),
|
||||
pos,
|
||||
)));
|
||||
}
|
||||
|
||||
// Raise error
|
||||
Err(Box::new(EvalAltResult::ErrorFunctionNotFound(
|
||||
format!("{} ({})", fn_name, types_list.join(", ")),
|
||||
pos,
|
||||
@ -628,11 +613,13 @@ impl Engine {
|
||||
|
||||
// Put arguments into scope as variables - variable name is copied
|
||||
scope.extend(
|
||||
// TODO - avoid copying variable name
|
||||
fn_def
|
||||
.params
|
||||
.iter()
|
||||
.zip(args.into_iter().map(|v| v.clone()))
|
||||
.zip(
|
||||
// Actually consume the arguments instead of cloning them
|
||||
args.into_iter().map(|v| mem::take(*v)),
|
||||
)
|
||||
.map(|(name, value)| (name.clone(), ScopeEntryType::Normal, value)),
|
||||
);
|
||||
|
||||
@ -659,7 +646,10 @@ impl Engine {
|
||||
fn_def
|
||||
.params
|
||||
.iter()
|
||||
.zip(args.into_iter().map(|v| v.clone()))
|
||||
.zip(
|
||||
// Actually consume the arguments instead of cloning them
|
||||
args.into_iter().map(|v| mem::take(*v)),
|
||||
)
|
||||
.map(|(name, value)| (name, ScopeEntryType::Normal, value)),
|
||||
);
|
||||
|
||||
@ -692,7 +682,7 @@ impl Engine {
|
||||
&self,
|
||||
fn_lib: &FunctionsLib,
|
||||
fn_name: &str,
|
||||
args: &mut [&mut Dynamic],
|
||||
args: &mut FnCallArgs,
|
||||
def_val: Option<&Dynamic>,
|
||||
pos: Position,
|
||||
level: usize,
|
||||
@ -710,7 +700,6 @@ impl Engine {
|
||||
pos,
|
||||
)))
|
||||
}
|
||||
|
||||
// Normal method call
|
||||
_ => self.call_fn_raw(None, fn_lib, fn_name, args, def_val, pos, level),
|
||||
}
|
||||
@ -737,20 +726,14 @@ impl Engine {
|
||||
)?;
|
||||
|
||||
// If new functions are defined within the eval string, it is an error
|
||||
if ast.1.len() > 0 {
|
||||
if ast.fn_lib().len() > 0 {
|
||||
return Err(Box::new(EvalAltResult::ErrorParsing(
|
||||
ParseErrorType::WrongFnDefinition.into_err(pos),
|
||||
)));
|
||||
}
|
||||
|
||||
#[cfg(feature = "sync")]
|
||||
{
|
||||
ast.1 = Arc::new(fn_lib.clone());
|
||||
}
|
||||
#[cfg(not(feature = "sync"))]
|
||||
{
|
||||
ast.1 = Rc::new(fn_lib.clone());
|
||||
}
|
||||
let statements = mem::take(ast.statements_mut());
|
||||
let ast = AST::new(statements, fn_lib.clone());
|
||||
|
||||
// Evaluate the AST
|
||||
self.eval_ast_with_scope_raw(scope, &ast)
|
||||
@ -787,48 +770,52 @@ impl Engine {
|
||||
Expr::Index(idx, idx_rhs, pos) => {
|
||||
let is_index = matches!(rhs, Expr::Index(_,_,_));
|
||||
|
||||
let indexed_val = self.get_indexed_mut(obj, idx_val, idx.position(), op_pos, false)?;
|
||||
let indexed_val = self.get_indexed_mut(fn_lib, obj, idx_val, idx.position(), op_pos, false)?;
|
||||
self.eval_dot_index_chain_helper(
|
||||
fn_lib, indexed_val, idx_rhs.as_ref(), idx_values, is_index, *pos, level, new_val
|
||||
)
|
||||
}
|
||||
// xxx[rhs] = new_val
|
||||
_ if new_val.is_some() => {
|
||||
let mut indexed_val = self.get_indexed_mut(obj, idx_val, rhs.position(), op_pos, true)?;
|
||||
let mut indexed_val = self.get_indexed_mut(fn_lib, obj, idx_val, rhs.position(), op_pos, true)?;
|
||||
indexed_val.set_value(new_val.unwrap(), rhs.position())?;
|
||||
Ok((Default::default(), true))
|
||||
}
|
||||
// xxx[rhs]
|
||||
_ => self
|
||||
.get_indexed_mut(obj, idx_val, rhs.position(), op_pos, false)
|
||||
.get_indexed_mut(fn_lib, obj, idx_val, rhs.position(), op_pos, false)
|
||||
.map(|v| (v.clone_into_dynamic(), false))
|
||||
}
|
||||
} else {
|
||||
match rhs {
|
||||
// xxx.fn_name(arg_expr_list)
|
||||
Expr::FnCall(fn_name, _, def_val, pos) => {
|
||||
Expr::FnCall(fn_name, None,_, def_val, pos) => {
|
||||
let mut args: Vec<_> = once(obj)
|
||||
.chain(idx_val.downcast_mut::<Array>().unwrap().iter_mut())
|
||||
.chain(idx_val.downcast_mut::<Vec<Dynamic>>().unwrap().iter_mut())
|
||||
.collect();
|
||||
let def_val = def_val.as_deref();
|
||||
// A function call is assumed to have side effects, so the value is changed
|
||||
// TODO - Remove assumption of side effects by checking whether the first parameter is &mut
|
||||
self.exec_fn_call(fn_lib, fn_name, &mut args, def_val, *pos, 0).map(|v| (v, true))
|
||||
}
|
||||
// xxx.module::fn_name(...) - syntax error
|
||||
Expr::FnCall(_,_,_,_,_) => unreachable!(),
|
||||
// {xxx:map}.id = ???
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::Property(id, pos) if obj.is::<Map>() && new_val.is_some() => {
|
||||
let mut indexed_val =
|
||||
self.get_indexed_mut(obj, id.to_string().into(), *pos, op_pos, true)?;
|
||||
self.get_indexed_mut(fn_lib, obj, id.to_string().into(), *pos, op_pos, true)?;
|
||||
indexed_val.set_value(new_val.unwrap(), rhs.position())?;
|
||||
Ok((Default::default(), true))
|
||||
}
|
||||
// {xxx:map}.id
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::Property(id, pos) if obj.is::<Map>() => {
|
||||
let indexed_val =
|
||||
self.get_indexed_mut(obj, id.to_string().into(), *pos, op_pos, false)?;
|
||||
self.get_indexed_mut(fn_lib, obj, id.to_string().into(), *pos, op_pos, false)?;
|
||||
Ok((indexed_val.clone_into_dynamic(), false))
|
||||
}
|
||||
// xxx.id = ???
|
||||
// xxx.id = ??? a
|
||||
Expr::Property(id, pos) if new_val.is_some() => {
|
||||
let fn_name = make_setter(id);
|
||||
let mut args = [obj, new_val.as_mut().unwrap()];
|
||||
@ -840,6 +827,7 @@ impl Engine {
|
||||
let mut args = [obj];
|
||||
self.exec_fn_call(fn_lib, &fn_name, &mut args, None, *pos, 0).map(|v| (v, false))
|
||||
}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
// {xxx:map}.idx_lhs[idx_expr]
|
||||
Expr::Index(dot_lhs, dot_rhs, pos) |
|
||||
// {xxx:map}.dot_lhs.rhs
|
||||
@ -847,7 +835,7 @@ impl Engine {
|
||||
let is_index = matches!(rhs, Expr::Index(_,_,_));
|
||||
|
||||
let indexed_val = if let Expr::Property(id, pos) = dot_lhs.as_ref() {
|
||||
self.get_indexed_mut(obj, id.to_string().into(), *pos, op_pos, false)?
|
||||
self.get_indexed_mut(fn_lib, obj, id.to_string().into(), *pos, op_pos, false)?
|
||||
} else {
|
||||
// Syntax error
|
||||
return Err(Box::new(EvalAltResult::ErrorDotExpr(
|
||||
@ -884,8 +872,13 @@ impl Engine {
|
||||
if may_be_changed {
|
||||
if let Expr::Property(id, pos) = dot_lhs.as_ref() {
|
||||
let fn_name = make_setter(id);
|
||||
// Re-use args because the first &mut parameter will not be consumed
|
||||
args[1] = indexed_val;
|
||||
self.exec_fn_call(fn_lib, &fn_name, &mut args, None, *pos, 0)?;
|
||||
self.exec_fn_call(fn_lib, &fn_name, &mut args, None, *pos, 0).or_else(|err| match *err {
|
||||
// If there is no setter, no need to feed it back because the property is read-only
|
||||
EvalAltResult::ErrorDotExpr(_,_) => Ok(Default::default()),
|
||||
err => Err(Box::new(err))
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
@ -919,21 +912,20 @@ impl Engine {
|
||||
|
||||
match dot_lhs {
|
||||
// id.??? or id[???]
|
||||
Expr::Variable(id, index, pos) => {
|
||||
let (target, typ) = match index {
|
||||
Some(i) if !state.always_search => scope.get_mut(scope.len() - i.get()),
|
||||
_ => search_scope(scope, id, *pos)?,
|
||||
};
|
||||
Expr::Variable(id, modules, index, pos) => {
|
||||
let index = if state.always_search { None } else { *index };
|
||||
let (target, typ) = search_scope(scope, id, modules, index, *pos)?;
|
||||
|
||||
// Constants cannot be modified
|
||||
match typ {
|
||||
ScopeEntryType::Module => unreachable!(),
|
||||
ScopeEntryType::Constant if new_val.is_some() => {
|
||||
return Err(Box::new(EvalAltResult::ErrorAssignmentToConstant(
|
||||
id.to_string(),
|
||||
*pos,
|
||||
)));
|
||||
}
|
||||
_ => (),
|
||||
ScopeEntryType::Constant | ScopeEntryType::Normal => (),
|
||||
}
|
||||
|
||||
let this_ptr = target.into();
|
||||
@ -976,14 +968,18 @@ impl Engine {
|
||||
level: usize,
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
match expr {
|
||||
Expr::FnCall(_, arg_exprs, _, _) => {
|
||||
Expr::FnCall(_, None, arg_exprs, _, _) => {
|
||||
let arg_values = arg_exprs
|
||||
.iter()
|
||||
.map(|arg_expr| self.eval_expr(scope, state, fn_lib, arg_expr, level))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
idx_values.push(arg_values)
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
idx_values.push(arg_values);
|
||||
#[cfg(feature = "no_index")]
|
||||
idx_values.push(Dynamic::from(arg_values));
|
||||
}
|
||||
Expr::FnCall(_, _, _, _, _) => unreachable!(),
|
||||
Expr::Property(_, _) => idx_values.push(()), // Store a placeholder - no need to copy the property name
|
||||
Expr::Index(lhs, rhs, _) | Expr::Dot(lhs, rhs, _) => {
|
||||
// Evaluate in left-to-right order
|
||||
@ -1006,8 +1002,9 @@ impl Engine {
|
||||
/// Get the value at the indexed position of a base type
|
||||
fn get_indexed_mut<'a>(
|
||||
&self,
|
||||
fn_lib: &FunctionsLib,
|
||||
val: &'a mut Dynamic,
|
||||
idx: Dynamic,
|
||||
mut idx: Dynamic,
|
||||
idx_pos: Position,
|
||||
op_pos: Position,
|
||||
create: bool,
|
||||
@ -1015,6 +1012,7 @@ impl Engine {
|
||||
let type_name = self.map_type_name(val.type_name());
|
||||
|
||||
match val {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Dynamic(Union::Array(arr)) => {
|
||||
// val_array[idx]
|
||||
let index = idx
|
||||
@ -1036,6 +1034,7 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Dynamic(Union::Map(map)) => {
|
||||
// val_map[idx]
|
||||
let index = idx
|
||||
@ -1051,6 +1050,7 @@ impl Engine {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Dynamic(Union::Str(s)) => {
|
||||
// val_string[idx]
|
||||
let index = idx
|
||||
@ -1080,11 +1080,18 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
let args = &mut [val, &mut idx];
|
||||
self.exec_fn_call(fn_lib, FUNC_INDEXER, args, None, op_pos, 0)
|
||||
.map(|v| v.into())
|
||||
.map_err(|_| {
|
||||
Box::new(EvalAltResult::ErrorIndexingType(
|
||||
// Error - cannot be indexed
|
||||
_ => Err(Box::new(EvalAltResult::ErrorIndexingType(
|
||||
type_name.to_string(),
|
||||
op_pos,
|
||||
))),
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1102,6 +1109,7 @@ impl Engine {
|
||||
let rhs_value = self.eval_expr(scope, state, fn_lib, rhs, level)?;
|
||||
|
||||
match rhs_value {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Dynamic(Union::Array(mut rhs_value)) => {
|
||||
let def_value = false.into();
|
||||
|
||||
@ -1109,9 +1117,10 @@ impl Engine {
|
||||
for value in rhs_value.iter_mut() {
|
||||
let args = &mut [&mut lhs_value, value];
|
||||
let def_value = Some(&def_value);
|
||||
let pos = rhs.position();
|
||||
|
||||
if self
|
||||
.call_fn_raw(None, fn_lib, "==", args, def_value, rhs.position(), level)?
|
||||
.call_fn_raw(None, fn_lib, "==", args, def_value, pos, level)?
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
@ -1121,6 +1130,7 @@ impl Engine {
|
||||
|
||||
Ok(false.into())
|
||||
}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Dynamic(Union::Map(rhs_value)) => match lhs_value {
|
||||
// Only allows String or char
|
||||
Dynamic(Union::Str(s)) => Ok(rhs_value.contains_key(s.as_ref()).into()),
|
||||
@ -1152,11 +1162,12 @@ impl Engine {
|
||||
Expr::FloatConstant(f, _) => Ok((*f).into()),
|
||||
Expr::StringConstant(s, _) => Ok(s.to_string().into()),
|
||||
Expr::CharConstant(c, _) => Ok((*c).into()),
|
||||
Expr::Variable(_, Some(index), _) if !state.always_search => {
|
||||
Ok(scope.get_mut(scope.len() - index.get()).0.clone())
|
||||
Expr::Variable(id, modules, index, pos) => {
|
||||
let index = if state.always_search { None } else { *index };
|
||||
let val = search_scope(scope, id, modules, index, *pos)?;
|
||||
Ok(val.0.clone())
|
||||
}
|
||||
Expr::Variable(id, _, pos) => search_scope(scope, id, *pos).map(|(v, _)| v.clone()),
|
||||
Expr::Property(_, _) => panic!("unexpected property."),
|
||||
Expr::Property(_, _) => unreachable!(),
|
||||
|
||||
// Statement block
|
||||
Expr::Stmt(stmt, _) => self.eval_stmt(scope, state, fn_lib, stmt, level),
|
||||
@ -1167,21 +1178,20 @@ impl Engine {
|
||||
|
||||
match lhs.as_ref() {
|
||||
// name = rhs
|
||||
Expr::Variable(name, _, pos) => match scope.get(name) {
|
||||
None => {
|
||||
return Err(Box::new(EvalAltResult::ErrorVariableNotFound(
|
||||
name.to_string(),
|
||||
*pos,
|
||||
)))
|
||||
}
|
||||
Some((_, ScopeEntryType::Constant)) => Err(Box::new(
|
||||
Expr::Variable(name, modules, index, pos) => {
|
||||
let index = if state.always_search { None } else { *index };
|
||||
match search_scope(scope, name, modules, index, *pos)? {
|
||||
(_, ScopeEntryType::Constant) => Err(Box::new(
|
||||
EvalAltResult::ErrorAssignmentToConstant(name.to_string(), *op_pos),
|
||||
)),
|
||||
Some((index, ScopeEntryType::Normal)) => {
|
||||
*scope.get_mut(index).0 = rhs_val;
|
||||
(value_ptr, ScopeEntryType::Normal) => {
|
||||
*value_ptr = rhs_val;
|
||||
Ok(Default::default())
|
||||
}
|
||||
},
|
||||
// End variable cannot be a module
|
||||
(_, ScopeEntryType::Module) => unreachable!(),
|
||||
}
|
||||
}
|
||||
// idx_lhs[idx_expr] = rhs
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(idx_lhs, idx_expr, op_pos) => {
|
||||
@ -1227,7 +1237,7 @@ impl Engine {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Array(contents, _) => Ok(Dynamic(Union::Array(Box::new(
|
||||
contents
|
||||
.into_iter()
|
||||
.iter()
|
||||
.map(|item| self.eval_expr(scope, state, fn_lib, item, level))
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
)))),
|
||||
@ -1235,15 +1245,16 @@ impl Engine {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::Map(contents, _) => Ok(Dynamic(Union::Map(Box::new(
|
||||
contents
|
||||
.into_iter()
|
||||
.iter()
|
||||
.map(|(key, expr, _)| {
|
||||
self.eval_expr(scope, state, fn_lib, &expr, level)
|
||||
self.eval_expr(scope, state, fn_lib, expr, level)
|
||||
.map(|val| (key.clone(), val))
|
||||
})
|
||||
.collect::<Result<HashMap<_, _>, _>>()?,
|
||||
)))),
|
||||
|
||||
Expr::FnCall(fn_name, arg_exprs, def_val, pos) => {
|
||||
// Normal function call
|
||||
Expr::FnCall(fn_name, None, arg_exprs, def_val, pos) => {
|
||||
let mut arg_values = arg_exprs
|
||||
.iter()
|
||||
.map(|expr| self.eval_expr(scope, state, fn_lib, expr, level))
|
||||
@ -1251,11 +1262,11 @@ impl Engine {
|
||||
|
||||
let mut args: Vec<_> = arg_values.iter_mut().collect();
|
||||
|
||||
// eval - only in function call style
|
||||
if fn_name.as_ref() == KEYWORD_EVAL
|
||||
&& args.len() == 1
|
||||
&& !self.has_override(fn_lib, KEYWORD_EVAL)
|
||||
{
|
||||
// eval - only in function call style
|
||||
let prev_len = scope.len();
|
||||
|
||||
// Evaluate the text string as a script
|
||||
@ -1268,11 +1279,45 @@ impl Engine {
|
||||
state.always_search = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
result
|
||||
} else {
|
||||
// Normal function call - except for eval (handled above)
|
||||
let def_value = def_val.as_deref();
|
||||
self.exec_fn_call(fn_lib, fn_name, &mut args, def_value, *pos, level)
|
||||
}
|
||||
}
|
||||
|
||||
// Normal function call - except for eval (handled above)
|
||||
self.exec_fn_call(fn_lib, fn_name, &mut args, def_val.as_deref(), *pos, level)
|
||||
// Module-qualified function call
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Expr::FnCall(fn_name, Some(modules), arg_exprs, def_val, pos) => {
|
||||
let modules = modules.as_ref();
|
||||
|
||||
let mut arg_values = arg_exprs
|
||||
.iter()
|
||||
.map(|expr| self.eval_expr(scope, state, fn_lib, expr, level))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let mut args: Vec<_> = arg_values.iter_mut().collect();
|
||||
|
||||
let (id, root_pos) = modules.get(0); // First module
|
||||
|
||||
let module = scope.find_module(id).ok_or_else(|| {
|
||||
Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos))
|
||||
})?;
|
||||
|
||||
// First search in script-defined functions (can override built-in)
|
||||
if let Some(fn_def) = module.get_qualified_fn_lib(fn_name, args.len(), modules)? {
|
||||
self.call_fn_from_lib(None, fn_lib, fn_def, &mut args, *pos, level)
|
||||
} else {
|
||||
// Then search in Rust functions
|
||||
let hash = calc_fn_hash(fn_name, args.iter().map(|a| a.type_id()));
|
||||
|
||||
match module.get_qualified_fn(fn_name, hash, modules, *pos) {
|
||||
Ok(func) => func(&mut args, *pos),
|
||||
Err(_) if def_val.is_some() => Ok(def_val.as_deref().unwrap().clone()),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Expr::In(lhs, rhs, _) => {
|
||||
@ -1313,7 +1358,7 @@ impl Engine {
|
||||
Expr::False(_) => Ok(false.into()),
|
||||
Expr::Unit(_) => Ok(().into()),
|
||||
|
||||
_ => panic!("should not appear: {:?}", expr),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1498,7 +1543,38 @@ impl Engine {
|
||||
Ok(Default::default())
|
||||
}
|
||||
|
||||
Stmt::Const(_, _, _) => panic!("constant expression not constant!"),
|
||||
// Const expression not constant
|
||||
Stmt::Const(_, _, _) => unreachable!(),
|
||||
|
||||
// Import statement
|
||||
Stmt::Import(expr, name, _) => {
|
||||
#[cfg(feature = "no_module")]
|
||||
unreachable!();
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
{
|
||||
if let Some(path) = self
|
||||
.eval_expr(scope, state, fn_lib, expr, level)?
|
||||
.try_cast::<String>()
|
||||
{
|
||||
if let Some(resolver) = self.module_resolver.as_ref() {
|
||||
let module = resolver.resolve(self, &path, expr.position())?;
|
||||
|
||||
// TODO - avoid copying module name in inner block?
|
||||
let mod_name = name.as_ref().clone();
|
||||
scope.push_module(mod_name, module);
|
||||
Ok(Default::default())
|
||||
} else {
|
||||
Err(Box::new(EvalAltResult::ErrorModuleNotFound(
|
||||
path,
|
||||
expr.position(),
|
||||
)))
|
||||
}
|
||||
} else {
|
||||
Err(Box::new(EvalAltResult::ErrorImportExpr(expr.position())))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ macro_rules! impl_args {
|
||||
fn into_vec(self) -> Vec<Dynamic> {
|
||||
let ($($p,)*) = self;
|
||||
|
||||
#[allow(unused_variables, unused_mut)]
|
||||
#[allow(unused_mut)]
|
||||
let mut v = Vec::new();
|
||||
$(v.push($p.into_dynamic());)*
|
||||
|
||||
@ -42,5 +42,4 @@ macro_rules! impl_args {
|
||||
};
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl_args!(A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V);
|
||||
|
@ -116,5 +116,4 @@ macro_rules! def_anonymous_fn {
|
||||
};
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
def_anonymous_fn!(A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V);
|
||||
|
@ -3,11 +3,12 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::any::{Dynamic, Variant};
|
||||
use crate::engine::{calc_fn_spec, Engine, FnCallArgs};
|
||||
use crate::engine::{Engine, FnCallArgs};
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::token::Position;
|
||||
use crate::utils::calc_fn_spec;
|
||||
|
||||
use crate::stdlib::{any::TypeId, boxed::Box, string::ToString};
|
||||
use crate::stdlib::{any::TypeId, boxed::Box, mem, string::ToString};
|
||||
|
||||
/// A trait to register custom plugins with the `Engine`.
|
||||
///
|
||||
@ -167,16 +168,19 @@ pub trait Plugin {
|
||||
pub struct Mut<T>(T);
|
||||
//pub struct Ref<T>(T);
|
||||
|
||||
/// Identity dereferencing function.
|
||||
/// Dereference into &mut.
|
||||
#[inline]
|
||||
pub fn identity<T>(data: &mut T) -> &mut T {
|
||||
data
|
||||
pub fn by_ref<T: Clone + 'static>(data: &mut Dynamic) -> &mut T {
|
||||
// Directly cast the &mut Dynamic into &mut T to access the underlying data.
|
||||
data.downcast_mut::<T>().unwrap()
|
||||
}
|
||||
|
||||
/// Clone dereferencing function.
|
||||
/// Dereference into value.
|
||||
#[inline]
|
||||
pub fn cloned<T: Clone>(data: &mut T) -> T {
|
||||
data.clone()
|
||||
pub fn by_value<T: Clone + 'static>(data: &mut Dynamic) -> T {
|
||||
// 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.
|
||||
mem::take(data).cast::<T>()
|
||||
}
|
||||
|
||||
#[cfg(feature = "plugins")]
|
||||
@ -194,7 +198,7 @@ macro_rules! count_args {
|
||||
|
||||
/// This macro creates a closure wrapping a registered function.
|
||||
macro_rules! make_func {
|
||||
($fn_name:ident : $fn:ident : $map:expr ; $($par:ident => $clone:expr),*) => {
|
||||
($fn_name:ident : $fn:ident : $map:expr ; $($par:ident => $convert:expr),*) => {
|
||||
// ^ function name
|
||||
// ^ function pointer
|
||||
// ^ result mapping function
|
||||
@ -212,13 +216,15 @@ macro_rules! make_func {
|
||||
#[allow(unused_variables, unused_mut)]
|
||||
let mut drain = args.iter_mut();
|
||||
$(
|
||||
// Downcast every element, return in case of a type mismatch
|
||||
let $par: &mut $par = drain.next().unwrap().downcast_mut().unwrap();
|
||||
// Downcast every element, panic in case of a type mismatch (which shouldn't happen).
|
||||
// Call the user-supplied function using ($convert) to access it either by value or by reference.
|
||||
let $par = ($convert)(drain.next().unwrap());
|
||||
)*
|
||||
|
||||
// Call the user-supplied function using ($clone) to
|
||||
// potentially clone the value, otherwise pass the reference.
|
||||
let r = $fn($(($clone)($par)),*);
|
||||
// Call the function with each parameter value
|
||||
let r = $fn($($par),*);
|
||||
|
||||
// Map the result
|
||||
$map(r, pos)
|
||||
};
|
||||
};
|
||||
@ -318,18 +324,17 @@ macro_rules! def_register {
|
||||
//def_register!(imp_pop $($par => $mark => $param),*);
|
||||
};
|
||||
($p0:ident $(, $p:ident)*) => {
|
||||
def_register!(imp $p0 => $p0 => $p0 => cloned $(, $p => $p => $p => cloned)*);
|
||||
def_register!(imp $p0 => Mut<$p0> => &mut $p0 => identity $(, $p => $p => $p => cloned)*);
|
||||
def_register!(imp $p0 => $p0 => $p0 => by_value $(, $p => $p => $p => by_value)*);
|
||||
def_register!(imp $p0 => Mut<$p0> => &mut $p0 => by_ref $(, $p => $p => $p => by_value)*);
|
||||
// handle the first parameter ^ first parameter passed through
|
||||
// ^ others passed by value (cloned)
|
||||
// ^ others passed by value (by_value)
|
||||
|
||||
// Currently does not support first argument which is a reference, as there will be
|
||||
// conflicting implementations since &T: Any and T: Any cannot be distinguished
|
||||
//def_register!(imp $p0 => Ref<$p0> => &$p0 => identity $(, $p => $p => $p => Clone::clone)*);
|
||||
//def_register!(imp $p0 => Ref<$p0> => &$p0 => by_ref $(, $p => $p => $p => by_value)*);
|
||||
|
||||
def_register!($($p),*);
|
||||
};
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
def_register!(A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V);
|
||||
|
13
src/lib.rs
13
src/lib.rs
@ -76,6 +76,7 @@ mod error;
|
||||
mod fn_call;
|
||||
mod fn_func;
|
||||
mod fn_register;
|
||||
mod module;
|
||||
mod optimize;
|
||||
pub mod packages;
|
||||
mod parser;
|
||||
@ -83,9 +84,10 @@ mod result;
|
||||
mod scope;
|
||||
mod stdlib;
|
||||
mod token;
|
||||
mod utils;
|
||||
|
||||
pub use any::Dynamic;
|
||||
pub use engine::{calc_fn_spec as calc_fn_hash, Engine};
|
||||
pub use engine::Engine;
|
||||
pub use error::{ParseError, ParseErrorType};
|
||||
pub use fn_call::FuncArgs;
|
||||
pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
|
||||
@ -95,6 +97,7 @@ pub use parser::{AST, INT};
|
||||
pub use result::EvalAltResult;
|
||||
pub use scope::Scope;
|
||||
pub use token::Position;
|
||||
pub use utils::calc_fn_spec as calc_fn_hash;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub use fn_func::Func;
|
||||
@ -108,5 +111,13 @@ pub use engine::Map;
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
pub use parser::FLOAT;
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub use module::{Module, ModuleResolver};
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub mod module_resolvers {
|
||||
pub use crate::module::resolvers::*;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
pub use optimize::OptimizationLevel;
|
||||
|
840
src/module.rs
Normal file
840
src/module.rs
Normal file
@ -0,0 +1,840 @@
|
||||
//! Module defining external-loaded modules for Rhai.
|
||||
#![cfg(not(feature = "no_module"))]
|
||||
|
||||
use crate::any::{Dynamic, Variant};
|
||||
use crate::calc_fn_hash;
|
||||
use crate::engine::{Engine, FnAny, FnCallArgs, FunctionsLib};
|
||||
use crate::parser::{FnDef, AST};
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
|
||||
use crate::token::Position;
|
||||
use crate::token::Token;
|
||||
use crate::utils::StaticVec;
|
||||
|
||||
use crate::stdlib::{
|
||||
any::TypeId,
|
||||
boxed::Box,
|
||||
collections::HashMap,
|
||||
fmt, mem,
|
||||
ops::{Deref, DerefMut},
|
||||
rc::Rc,
|
||||
string::{String, ToString},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
/// A trait that encapsulates a module resolution service.
|
||||
pub trait ModuleResolver {
|
||||
/// Resolve a module based on a path string.
|
||||
fn resolve(
|
||||
&self,
|
||||
engine: &Engine,
|
||||
path: &str,
|
||||
pos: Position,
|
||||
) -> Result<Module, Box<EvalAltResult>>;
|
||||
}
|
||||
|
||||
/// Return type of module-level Rust function.
|
||||
type FuncReturn<T> = Result<T, Box<EvalAltResult>>;
|
||||
|
||||
/// An imported module, which may contain variables, sub-modules,
|
||||
/// external Rust functions, and script-defined functions.
|
||||
///
|
||||
/// Not available under the `no_module` feature.
|
||||
#[derive(Default, Clone)]
|
||||
pub struct Module {
|
||||
/// Sub-modules.
|
||||
modules: HashMap<String, Module>,
|
||||
/// Module variables, including sub-modules.
|
||||
variables: HashMap<String, Dynamic>,
|
||||
|
||||
/// External Rust functions.
|
||||
#[cfg(not(feature = "sync"))]
|
||||
functions: HashMap<u64, Rc<Box<FnAny>>>,
|
||||
/// External Rust functions.
|
||||
#[cfg(feature = "sync")]
|
||||
functions: HashMap<u64, Arc<Box<FnAny>>>,
|
||||
|
||||
/// Script-defined functions.
|
||||
fn_lib: FunctionsLib,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Module {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"<module {:?}, functions={}, lib={}>",
|
||||
self.variables,
|
||||
self.functions.len(),
|
||||
self.fn_lib.len()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Module {
|
||||
/// Create a new module.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Module;
|
||||
///
|
||||
/// let mut module = Module::new();
|
||||
/// module.set_var("answer", 42_i64);
|
||||
/// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42);
|
||||
/// ```
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Does a variable exist in the module?
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Module;
|
||||
///
|
||||
/// let mut module = Module::new();
|
||||
/// module.set_var("answer", 42_i64);
|
||||
/// assert!(module.contains_var("answer"));
|
||||
/// ```
|
||||
pub fn contains_var(&self, name: &str) -> bool {
|
||||
self.variables.contains_key(name)
|
||||
}
|
||||
|
||||
/// Get the value of a module variable.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Module;
|
||||
///
|
||||
/// let mut module = Module::new();
|
||||
/// module.set_var("answer", 42_i64);
|
||||
/// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42);
|
||||
/// ```
|
||||
pub fn get_var_value<T: Variant + Clone>(&self, name: &str) -> Option<T> {
|
||||
self.get_var(name).and_then(|v| v.try_cast::<T>())
|
||||
}
|
||||
|
||||
/// Get a module variable as a `Dynamic`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Module;
|
||||
///
|
||||
/// let mut module = Module::new();
|
||||
/// module.set_var("answer", 42_i64);
|
||||
/// assert_eq!(module.get_var("answer").unwrap().cast::<i64>(), 42);
|
||||
/// ```
|
||||
pub fn get_var(&self, name: &str) -> Option<Dynamic> {
|
||||
self.variables.get(name).cloned()
|
||||
}
|
||||
|
||||
/// Get a mutable reference to a module variable.
|
||||
pub fn get_var_mut(&mut self, name: &str) -> Option<&mut Dynamic> {
|
||||
self.variables.get_mut(name)
|
||||
}
|
||||
|
||||
/// Set a variable into the module.
|
||||
///
|
||||
/// If there is an existing variable of the same name, it is replaced.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Module;
|
||||
///
|
||||
/// let mut module = Module::new();
|
||||
/// module.set_var("answer", 42_i64);
|
||||
/// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42);
|
||||
/// ```
|
||||
pub fn set_var<K: Into<String>, T: Into<Dynamic>>(&mut self, name: K, value: T) {
|
||||
self.variables.insert(name.into(), value.into());
|
||||
}
|
||||
|
||||
/// Get a mutable reference to a modules-qualified variable.
|
||||
pub(crate) fn get_qualified_var_mut(
|
||||
&mut self,
|
||||
name: &str,
|
||||
modules: &StaticVec<(String, Position)>,
|
||||
pos: Position,
|
||||
) -> Result<&mut Dynamic, Box<EvalAltResult>> {
|
||||
Ok(self
|
||||
.get_qualified_module_mut(modules)?
|
||||
.get_var_mut(name)
|
||||
.ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))?)
|
||||
}
|
||||
|
||||
/// Does a sub-module exist in the module?
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Module;
|
||||
///
|
||||
/// let mut module = Module::new();
|
||||
/// let sub_module = Module::new();
|
||||
/// module.set_sub_module("question", sub_module);
|
||||
/// assert!(module.contains_sub_module("question"));
|
||||
/// ```
|
||||
pub fn contains_sub_module(&self, name: &str) -> bool {
|
||||
self.modules.contains_key(name)
|
||||
}
|
||||
|
||||
/// Get a sub-module.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Module;
|
||||
///
|
||||
/// let mut module = Module::new();
|
||||
/// let sub_module = Module::new();
|
||||
/// module.set_sub_module("question", sub_module);
|
||||
/// assert!(module.get_sub_module("question").is_some());
|
||||
/// ```
|
||||
pub fn get_sub_module(&self, name: &str) -> Option<&Module> {
|
||||
self.modules.get(name)
|
||||
}
|
||||
|
||||
/// Get a mutable reference to a sub-module.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Module;
|
||||
///
|
||||
/// let mut module = Module::new();
|
||||
/// let sub_module = Module::new();
|
||||
/// module.set_sub_module("question", sub_module);
|
||||
/// assert!(module.get_sub_module_mut("question").is_some());
|
||||
/// ```
|
||||
pub fn get_sub_module_mut(&mut self, name: &str) -> Option<&mut Module> {
|
||||
self.modules.get_mut(name)
|
||||
}
|
||||
|
||||
/// Set a sub-module into the module.
|
||||
///
|
||||
/// If there is an existing sub-module of the same name, it is replaced.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Module;
|
||||
///
|
||||
/// let mut module = Module::new();
|
||||
/// let sub_module = Module::new();
|
||||
/// module.set_sub_module("question", sub_module);
|
||||
/// assert!(module.get_sub_module("question").is_some());
|
||||
/// ```
|
||||
pub fn set_sub_module<K: Into<String>>(&mut self, name: K, sub_module: Module) {
|
||||
self.modules.insert(name.into(), sub_module.into());
|
||||
}
|
||||
|
||||
/// Get a mutable reference to a modules chain.
|
||||
/// The first module is always skipped and assumed to be the same as `self`.
|
||||
pub(crate) fn get_qualified_module_mut(
|
||||
&mut self,
|
||||
modules: &StaticVec<(String, Position)>,
|
||||
) -> Result<&mut Module, Box<EvalAltResult>> {
|
||||
let mut drain = modules.iter();
|
||||
drain.next().unwrap(); // Skip first module
|
||||
|
||||
let mut module = self;
|
||||
|
||||
for (id, id_pos) in drain {
|
||||
module = module
|
||||
.get_sub_module_mut(id)
|
||||
.ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *id_pos)))?;
|
||||
}
|
||||
|
||||
Ok(module)
|
||||
}
|
||||
|
||||
/// Does the particular Rust function exist in the module?
|
||||
///
|
||||
/// The `u64` hash is calculated by the function `crate::calc_fn_hash`.
|
||||
/// It is also returned by the `set_fn_XXX` calls.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Module;
|
||||
///
|
||||
/// let mut module = Module::new();
|
||||
/// let hash = module.set_fn_0("calc", || Ok(42_i64));
|
||||
/// assert!(module.contains_fn(hash));
|
||||
/// ```
|
||||
pub fn contains_fn(&self, hash: u64) -> bool {
|
||||
self.functions.contains_key(&hash)
|
||||
}
|
||||
|
||||
/// Set a Rust function into the module, returning a hash key.
|
||||
///
|
||||
/// If there is an existing Rust function of the same hash, it is replaced.
|
||||
pub fn set_fn(&mut self, fn_name: &str, params: &[TypeId], func: Box<FnAny>) -> u64 {
|
||||
let hash = calc_fn_hash(fn_name, params.iter().cloned());
|
||||
|
||||
#[cfg(not(feature = "sync"))]
|
||||
self.functions.insert(hash, Rc::new(func));
|
||||
#[cfg(feature = "sync")]
|
||||
self.functions.insert(hash, Arc::new(func));
|
||||
|
||||
hash
|
||||
}
|
||||
|
||||
/// Set a Rust function taking no parameters into the module, returning a hash key.
|
||||
///
|
||||
/// If there is a similar existing Rust function, it is replaced.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Module;
|
||||
///
|
||||
/// let mut module = Module::new();
|
||||
/// let hash = module.set_fn_0("calc", || Ok(42_i64));
|
||||
/// assert!(module.get_fn(hash).is_some());
|
||||
/// ```
|
||||
pub fn set_fn_0<T: Into<Dynamic>>(
|
||||
&mut self,
|
||||
fn_name: &str,
|
||||
#[cfg(not(feature = "sync"))] func: impl Fn() -> FuncReturn<T> + 'static,
|
||||
#[cfg(feature = "sync")] func: impl Fn() -> FuncReturn<T> + Send + Sync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |_: &mut FnCallArgs, pos| {
|
||||
func()
|
||||
.map(|v| v.into())
|
||||
.map_err(|err| EvalAltResult::set_position(err, pos))
|
||||
};
|
||||
let arg_types = &[];
|
||||
self.set_fn(fn_name, arg_types, Box::new(f))
|
||||
}
|
||||
|
||||
/// Set a Rust function taking one parameter into the module, returning a hash key.
|
||||
///
|
||||
/// If there is a similar existing Rust function, it is replaced.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Module;
|
||||
///
|
||||
/// let mut module = Module::new();
|
||||
/// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1));
|
||||
/// assert!(module.get_fn(hash).is_some());
|
||||
/// ```
|
||||
pub fn set_fn_1<A: Variant + Clone, T: Into<Dynamic>>(
|
||||
&mut self,
|
||||
fn_name: &str,
|
||||
#[cfg(not(feature = "sync"))] func: impl Fn(A) -> FuncReturn<T> + 'static,
|
||||
#[cfg(feature = "sync")] func: impl Fn(A) -> FuncReturn<T> + Send + Sync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |args: &mut FnCallArgs, pos| {
|
||||
func(mem::take(args[0]).cast::<A>())
|
||||
.map(|v| v.into())
|
||||
.map_err(|err| EvalAltResult::set_position(err, pos))
|
||||
};
|
||||
let arg_types = &[TypeId::of::<A>()];
|
||||
self.set_fn(fn_name, arg_types, Box::new(f))
|
||||
}
|
||||
|
||||
/// Set a Rust function taking one mutable parameter into the module, returning a hash key.
|
||||
///
|
||||
/// If there is a similar existing Rust function, it is replaced.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Module;
|
||||
///
|
||||
/// let mut module = Module::new();
|
||||
/// let hash = module.set_fn_1_mut("calc", |x: &mut i64| { *x += 1; Ok(*x) });
|
||||
/// assert!(module.get_fn(hash).is_some());
|
||||
/// ```
|
||||
pub fn set_fn_1_mut<A: Variant + Clone, T: Into<Dynamic>>(
|
||||
&mut self,
|
||||
fn_name: &str,
|
||||
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A) -> FuncReturn<T> + 'static,
|
||||
#[cfg(feature = "sync")] func: impl Fn(&mut A) -> FuncReturn<T> + Send + Sync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |args: &mut FnCallArgs, pos| {
|
||||
func(args[0].downcast_mut::<A>().unwrap())
|
||||
.map(|v| v.into())
|
||||
.map_err(|err| EvalAltResult::set_position(err, pos))
|
||||
};
|
||||
let arg_types = &[TypeId::of::<A>()];
|
||||
self.set_fn(fn_name, arg_types, Box::new(f))
|
||||
}
|
||||
|
||||
/// Set a Rust function taking two parameters into the module, returning a hash key.
|
||||
///
|
||||
/// If there is a similar existing Rust function, it is replaced.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Module;
|
||||
///
|
||||
/// let mut module = Module::new();
|
||||
/// let hash = module.set_fn_2("calc", |x: i64, y: String| {
|
||||
/// Ok(x + y.len() as i64)
|
||||
/// });
|
||||
/// assert!(module.get_fn(hash).is_some());
|
||||
/// ```
|
||||
pub fn set_fn_2<A: Variant + Clone, B: Variant + Clone, T: Into<Dynamic>>(
|
||||
&mut self,
|
||||
fn_name: &str,
|
||||
#[cfg(not(feature = "sync"))] func: impl Fn(A, B) -> FuncReturn<T> + 'static,
|
||||
#[cfg(feature = "sync")] func: impl Fn(A, B) -> FuncReturn<T> + Send + Sync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |args: &mut FnCallArgs, pos| {
|
||||
let a = mem::take(args[0]).cast::<A>();
|
||||
let b = mem::take(args[1]).cast::<B>();
|
||||
|
||||
func(a, b)
|
||||
.map(|v| v.into())
|
||||
.map_err(|err| EvalAltResult::set_position(err, pos))
|
||||
};
|
||||
let arg_types = &[TypeId::of::<A>(), TypeId::of::<B>()];
|
||||
self.set_fn(fn_name, arg_types, Box::new(f))
|
||||
}
|
||||
|
||||
/// Set a Rust function taking two parameters (the first one mutable) into the module,
|
||||
/// returning a hash key.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Module;
|
||||
///
|
||||
/// let mut module = Module::new();
|
||||
/// let hash = module.set_fn_2_mut("calc", |x: &mut i64, y: String| {
|
||||
/// *x += y.len() as i64; Ok(*x)
|
||||
/// });
|
||||
/// assert!(module.get_fn(hash).is_some());
|
||||
/// ```
|
||||
pub fn set_fn_2_mut<A: Variant + Clone, B: Variant + Clone, T: Into<Dynamic>>(
|
||||
&mut self,
|
||||
fn_name: &str,
|
||||
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> FuncReturn<T> + 'static,
|
||||
#[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> FuncReturn<T> + Send + Sync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |args: &mut FnCallArgs, pos| {
|
||||
let b = mem::take(args[1]).cast::<B>();
|
||||
let a = args[0].downcast_mut::<A>().unwrap();
|
||||
|
||||
func(a, b)
|
||||
.map(|v| v.into())
|
||||
.map_err(|err| EvalAltResult::set_position(err, pos))
|
||||
};
|
||||
let arg_types = &[TypeId::of::<A>(), TypeId::of::<B>()];
|
||||
self.set_fn(fn_name, arg_types, Box::new(f))
|
||||
}
|
||||
|
||||
/// Set a Rust function taking three parameters into the module, returning a hash key.
|
||||
///
|
||||
/// If there is a similar existing Rust function, it is replaced.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Module;
|
||||
///
|
||||
/// let mut module = Module::new();
|
||||
/// let hash = module.set_fn_3("calc", |x: i64, y: String, z: i64| {
|
||||
/// Ok(x + y.len() as i64 + z)
|
||||
/// });
|
||||
/// assert!(module.get_fn(hash).is_some());
|
||||
/// ```
|
||||
pub fn set_fn_3<
|
||||
A: Variant + Clone,
|
||||
B: Variant + Clone,
|
||||
C: Variant + Clone,
|
||||
T: Into<Dynamic>,
|
||||
>(
|
||||
&mut self,
|
||||
fn_name: &str,
|
||||
#[cfg(not(feature = "sync"))] func: impl Fn(A, B, C) -> FuncReturn<T> + 'static,
|
||||
#[cfg(feature = "sync")] func: impl Fn(A, B, C) -> FuncReturn<T> + Send + Sync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |args: &mut FnCallArgs, pos| {
|
||||
let a = mem::take(args[0]).cast::<A>();
|
||||
let b = mem::take(args[1]).cast::<B>();
|
||||
let c = mem::take(args[2]).cast::<C>();
|
||||
|
||||
func(a, b, c)
|
||||
.map(|v| v.into())
|
||||
.map_err(|err| EvalAltResult::set_position(err, pos))
|
||||
};
|
||||
let arg_types = &[TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
|
||||
self.set_fn(fn_name, arg_types, Box::new(f))
|
||||
}
|
||||
|
||||
/// Set a Rust function taking three parameters (the first one mutable) into the module,
|
||||
/// returning a hash key.
|
||||
///
|
||||
/// If there is a similar existing Rust function, it is replaced.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Module;
|
||||
///
|
||||
/// let mut module = Module::new();
|
||||
/// let hash = module.set_fn_3_mut("calc", |x: &mut i64, y: String, z: i64| {
|
||||
/// *x += y.len() as i64 + z; Ok(*x)
|
||||
/// });
|
||||
/// assert!(module.get_fn(hash).is_some());
|
||||
/// ```
|
||||
pub fn set_fn_3_mut<
|
||||
A: Variant + Clone,
|
||||
B: Variant + Clone,
|
||||
C: Variant + Clone,
|
||||
T: Into<Dynamic>,
|
||||
>(
|
||||
&mut self,
|
||||
fn_name: &str,
|
||||
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B, C) -> FuncReturn<T> + 'static,
|
||||
#[cfg(feature = "sync")] func: impl Fn(&mut A, B, C) -> FuncReturn<T> + Send + Sync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |args: &mut FnCallArgs, pos| {
|
||||
let b = mem::take(args[1]).cast::<B>();
|
||||
let c = mem::take(args[2]).cast::<C>();
|
||||
let a = args[0].downcast_mut::<A>().unwrap();
|
||||
|
||||
func(a, b, c)
|
||||
.map(|v| v.into())
|
||||
.map_err(|err| EvalAltResult::set_position(err, pos))
|
||||
};
|
||||
let arg_types = &[TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
|
||||
self.set_fn(fn_name, arg_types, Box::new(f))
|
||||
}
|
||||
|
||||
/// Get a Rust function.
|
||||
///
|
||||
/// The `u64` hash is calculated by the function `crate::calc_fn_hash`.
|
||||
/// It is also returned by the `set_fn_XXX` calls.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Module;
|
||||
///
|
||||
/// let mut module = Module::new();
|
||||
/// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1));
|
||||
/// assert!(module.get_fn(hash).is_some());
|
||||
/// ```
|
||||
pub fn get_fn(&self, hash: u64) -> Option<&Box<FnAny>> {
|
||||
self.functions.get(&hash).map(|v| v.as_ref())
|
||||
}
|
||||
|
||||
/// Get a modules-qualified function.
|
||||
///
|
||||
/// 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_qualified_fn(
|
||||
&mut self,
|
||||
name: &str,
|
||||
hash: u64,
|
||||
modules: &StaticVec<(String, Position)>,
|
||||
pos: Position,
|
||||
) -> Result<&Box<FnAny>, Box<EvalAltResult>> {
|
||||
Ok(self
|
||||
.get_qualified_module_mut(modules)?
|
||||
.get_fn(hash)
|
||||
.ok_or_else(|| {
|
||||
let mut fn_name: String = Default::default();
|
||||
|
||||
modules.iter().for_each(|(n, _)| {
|
||||
fn_name.push_str(n);
|
||||
fn_name.push_str(Token::DoubleColon.syntax().as_ref());
|
||||
});
|
||||
|
||||
fn_name.push_str(name);
|
||||
|
||||
Box::new(EvalAltResult::ErrorFunctionNotFound(fn_name, pos))
|
||||
})?)
|
||||
}
|
||||
|
||||
/// Get the script-defined functions.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Module;
|
||||
///
|
||||
/// let mut module = Module::new();
|
||||
/// assert_eq!(module.get_fn_lib().len(), 0);
|
||||
/// ```
|
||||
pub fn get_fn_lib(&self) -> &FunctionsLib {
|
||||
&self.fn_lib
|
||||
}
|
||||
|
||||
/// Get a modules-qualified functions library.
|
||||
pub(crate) fn get_qualified_fn_lib(
|
||||
&mut self,
|
||||
name: &str,
|
||||
args: usize,
|
||||
modules: &StaticVec<(String, Position)>,
|
||||
) -> Result<Option<&FnDef>, Box<EvalAltResult>> {
|
||||
Ok(self
|
||||
.get_qualified_module_mut(modules)?
|
||||
.fn_lib
|
||||
.get_function(name, args))
|
||||
}
|
||||
|
||||
/// Create a new `Module` by evaluating an `AST`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// use rhai::{Engine, Module};
|
||||
///
|
||||
/// let engine = Engine::new();
|
||||
/// let ast = engine.compile("let answer = 42;")?;
|
||||
/// let module = Module::eval_ast_as_new(&ast, &engine)?;
|
||||
/// assert!(module.contains_var("answer"));
|
||||
/// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn eval_ast_as_new(ast: &AST, engine: &Engine) -> FuncReturn<Self> {
|
||||
// Use new scope
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Run the script
|
||||
engine.eval_ast_with_scope_raw(&mut scope, &ast)?;
|
||||
|
||||
// Create new module
|
||||
let mut module = Module::new();
|
||||
|
||||
scope.into_iter().for_each(
|
||||
|ScopeEntry {
|
||||
name, typ, value, ..
|
||||
}| {
|
||||
match typ {
|
||||
// Variables left in the scope become module variables
|
||||
ScopeEntryType::Normal | ScopeEntryType::Constant => {
|
||||
module.variables.insert(name.into_owned(), value);
|
||||
}
|
||||
// Modules left in the scope become sub-modules
|
||||
ScopeEntryType::Module => {
|
||||
module
|
||||
.modules
|
||||
.insert(name.into_owned(), value.cast::<Module>());
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
module.fn_lib = module.fn_lib.merge(ast.fn_lib());
|
||||
|
||||
Ok(module)
|
||||
}
|
||||
}
|
||||
|
||||
/// Re-export module resolvers.
|
||||
pub mod resolvers {
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
pub use super::file::FileModuleResolver;
|
||||
pub use super::stat::StaticModuleResolver;
|
||||
}
|
||||
|
||||
/// Script file-based module resolver.
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
mod file {
|
||||
use super::*;
|
||||
use crate::stdlib::path::PathBuf;
|
||||
|
||||
/// A module resolution service that loads module script files from the file system.
|
||||
///
|
||||
/// The `new_with_path` and `new_with_path_and_extension` constructor functions
|
||||
/// allow specification of a base directory with module path used as a relative path offset
|
||||
/// to the base directory. The script file is then forced to be in a specified extension
|
||||
/// (default `.rhai`).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Engine;
|
||||
/// use rhai::module_resolvers::FileModuleResolver;
|
||||
///
|
||||
/// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory
|
||||
/// // with file extension '.x'.
|
||||
/// let resolver = FileModuleResolver::new_with_path_and_extension("./scripts", "x");
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
/// engine.set_module_resolver(Some(resolver));
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
|
||||
pub struct FileModuleResolver {
|
||||
path: PathBuf,
|
||||
extension: String,
|
||||
}
|
||||
|
||||
impl FileModuleResolver {
|
||||
/// Create a new `FileModuleResolver` with a specific base path.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Engine;
|
||||
/// use rhai::module_resolvers::FileModuleResolver;
|
||||
///
|
||||
/// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory
|
||||
/// // with file extension '.rhai' (the default).
|
||||
/// let resolver = FileModuleResolver::new_with_path("./scripts");
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
/// engine.set_module_resolver(Some(resolver));
|
||||
/// ```
|
||||
pub fn new_with_path<P: Into<PathBuf>>(path: P) -> Self {
|
||||
Self::new_with_path_and_extension(path, "rhai")
|
||||
}
|
||||
|
||||
/// Create a new `FileModuleResolver` with a specific base path and file extension.
|
||||
///
|
||||
/// The default extension is `.rhai`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Engine;
|
||||
/// use rhai::module_resolvers::FileModuleResolver;
|
||||
///
|
||||
/// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory
|
||||
/// // with file extension '.x'.
|
||||
/// let resolver = FileModuleResolver::new_with_path_and_extension("./scripts", "x");
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
/// engine.set_module_resolver(Some(resolver));
|
||||
/// ```
|
||||
pub fn new_with_path_and_extension<P: Into<PathBuf>, E: Into<String>>(
|
||||
path: P,
|
||||
extension: E,
|
||||
) -> Self {
|
||||
Self {
|
||||
path: path.into(),
|
||||
extension: extension.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `FileModuleResolver` with the current directory as base path.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Engine;
|
||||
/// use rhai::module_resolvers::FileModuleResolver;
|
||||
///
|
||||
/// // Create a new 'FileModuleResolver' loading scripts from the current directory
|
||||
/// // with file extension '.rhai' (the default).
|
||||
/// let resolver = FileModuleResolver::new();
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
/// engine.set_module_resolver(Some(resolver));
|
||||
/// ```
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleResolver for FileModuleResolver {
|
||||
fn resolve(
|
||||
&self,
|
||||
engine: &Engine,
|
||||
path: &str,
|
||||
pos: Position,
|
||||
) -> Result<Module, Box<EvalAltResult>> {
|
||||
// Construct the script file path
|
||||
let mut file_path = self.path.clone();
|
||||
file_path.push(path);
|
||||
file_path.set_extension(&self.extension); // Force extension
|
||||
|
||||
// Compile it
|
||||
let ast = engine
|
||||
.compile_file(file_path)
|
||||
.map_err(|err| EvalAltResult::set_position(err, pos))?;
|
||||
|
||||
Module::eval_ast_as_new(&ast, engine)
|
||||
.map_err(|err| EvalAltResult::set_position(err, pos))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Static module resolver.
|
||||
mod stat {
|
||||
use super::*;
|
||||
|
||||
/// A module resolution service that serves modules added into it.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::{Engine, Module};
|
||||
/// use rhai::module_resolvers::StaticModuleResolver;
|
||||
///
|
||||
/// let mut resolver = StaticModuleResolver::new();
|
||||
///
|
||||
/// let module = Module::new();
|
||||
/// resolver.insert("hello".to_string(), module);
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
/// engine.set_module_resolver(Some(resolver));
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct StaticModuleResolver(HashMap<String, Module>);
|
||||
|
||||
impl StaticModuleResolver {
|
||||
/// Create a new `StaticModuleResolver`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::{Engine, Module};
|
||||
/// use rhai::module_resolvers::StaticModuleResolver;
|
||||
///
|
||||
/// let mut resolver = StaticModuleResolver::new();
|
||||
///
|
||||
/// let module = Module::new();
|
||||
/// resolver.insert("hello".to_string(), module);
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
/// engine.set_module_resolver(Some(resolver));
|
||||
/// ```
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for StaticModuleResolver {
|
||||
type Target = HashMap<String, Module>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for StaticModuleResolver {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleResolver for StaticModuleResolver {
|
||||
fn resolve(
|
||||
&self,
|
||||
_: &Engine,
|
||||
path: &str,
|
||||
pos: Position,
|
||||
) -> Result<Module, Box<EvalAltResult>> {
|
||||
self.0
|
||||
.get(path)
|
||||
.cloned()
|
||||
.ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(path.to_string(), pos)))
|
||||
}
|
||||
}
|
||||
}
|
@ -13,9 +13,7 @@ use crate::token::Position;
|
||||
use crate::stdlib::{
|
||||
boxed::Box,
|
||||
collections::HashMap,
|
||||
rc::Rc,
|
||||
string::{String, ToString},
|
||||
sync::Arc,
|
||||
vec,
|
||||
vec::Vec,
|
||||
};
|
||||
@ -231,6 +229,8 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
|
||||
}
|
||||
// let id;
|
||||
Stmt::Let(_, None, _) => stmt,
|
||||
// import expr as id;
|
||||
Stmt::Import(expr, id, pos) => Stmt::Import(Box::new(optimize_expr(*expr, state)), id, pos),
|
||||
// { block }
|
||||
Stmt::Block(block, pos) => {
|
||||
let orig_len = block.len(); // Original number of statements in the block, for change detection
|
||||
@ -260,7 +260,7 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
|
||||
result.push(stmt);
|
||||
}
|
||||
|
||||
// Remove all let statements at the end of a block - the new variables will go away anyway.
|
||||
// Remove all let/import statements at the end of a block - the new variables will go away anyway.
|
||||
// But be careful only remove ones that have no initial values or have values that are pure expressions,
|
||||
// otherwise there may be side effects.
|
||||
let mut removed = false;
|
||||
@ -268,7 +268,8 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
|
||||
while let Some(expr) = result.pop() {
|
||||
match expr {
|
||||
Stmt::Let(_, None, _) => removed = true,
|
||||
Stmt::Let(_, Some(val_expr), _) if val_expr.is_pure() => removed = true,
|
||||
Stmt::Let(_, Some(val_expr), _) => removed = val_expr.is_pure(),
|
||||
Stmt::Import(expr, _, _) => removed = expr.is_pure(),
|
||||
_ => {
|
||||
result.push(expr);
|
||||
break;
|
||||
@ -323,6 +324,8 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
|
||||
state.set_dirty();
|
||||
Stmt::Noop(pos)
|
||||
}
|
||||
// Only one let/import statement - leave it alone
|
||||
[Stmt::Let(_, _, _)] | [Stmt::Import(_, _, _)] => Stmt::Block(result, pos),
|
||||
// Only one statement - promote
|
||||
[_] => {
|
||||
state.set_dirty();
|
||||
@ -368,15 +371,12 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
//id = id2 = expr2
|
||||
Expr::Assignment(id2, expr2, pos2) => match (*id, *id2) {
|
||||
// var = var = expr2 -> var = expr2
|
||||
(Expr::Variable(var, sp, _), Expr::Variable(var2, sp2, _)) if var == var2 && sp == sp2 => {
|
||||
(Expr::Variable(var, None, sp, _), Expr::Variable(var2, None, sp2, _))
|
||||
if var == var2 && sp == sp2 =>
|
||||
{
|
||||
// Assignment to the same variable - fold
|
||||
state.set_dirty();
|
||||
|
||||
Expr::Assignment(
|
||||
Box::new(Expr::Variable(var, sp, pos)),
|
||||
Box::new(optimize_expr(*expr2, state)),
|
||||
pos,
|
||||
)
|
||||
Expr::Assignment(Box::new(Expr::Variable(var, None, sp, pos)), Box::new(optimize_expr(*expr2, state)), pos)
|
||||
}
|
||||
// id1 = id2 = expr2
|
||||
(id1, id2) => Expr::Assignment(
|
||||
@ -552,18 +552,18 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
},
|
||||
|
||||
// Do not call some special keywords
|
||||
Expr::FnCall(id, args, def_value, pos) if DONT_EVAL_KEYWORDS.contains(&id.as_ref().as_ref())=>
|
||||
Expr::FnCall(id, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos),
|
||||
Expr::FnCall(id, None, args, def_value, pos) if DONT_EVAL_KEYWORDS.contains(&id.as_ref().as_ref())=>
|
||||
Expr::FnCall(id, None, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos),
|
||||
|
||||
// Eagerly call functions
|
||||
Expr::FnCall(id, args, def_value, pos)
|
||||
Expr::FnCall(id, None, args, def_value, pos)
|
||||
if state.optimization_level == OptimizationLevel::Full // full optimizations
|
||||
&& args.iter().all(|expr| expr.is_constant()) // all arguments are constants
|
||||
=> {
|
||||
// First search in script-defined functions (can override built-in)
|
||||
if state.fn_lib.iter().find(|(name, len)| name == id.as_ref() && *len == args.len()).is_some() {
|
||||
// A script-defined function overrides the built-in function - do not make the call
|
||||
return Expr::FnCall(id, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos);
|
||||
return Expr::FnCall(id, None, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos);
|
||||
}
|
||||
|
||||
let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect();
|
||||
@ -594,16 +594,16 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
})
|
||||
).unwrap_or_else(||
|
||||
// Optimize function call arguments
|
||||
Expr::FnCall(id, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos)
|
||||
Expr::FnCall(id, None, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos)
|
||||
)
|
||||
}
|
||||
|
||||
// id(args ..) -> optimize function call arguments
|
||||
Expr::FnCall(id, args, def_value, pos) =>
|
||||
Expr::FnCall(id, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos),
|
||||
Expr::FnCall(id, modules, args, def_value, pos) =>
|
||||
Expr::FnCall(id, modules, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos),
|
||||
|
||||
// constant-name
|
||||
Expr::Variable(name, _, pos) if state.contains_constant(&name) => {
|
||||
Expr::Variable(name, None, _, pos) if state.contains_constant(&name) => {
|
||||
state.set_dirty();
|
||||
|
||||
// Replace constant with value
|
||||
@ -669,7 +669,10 @@ fn optimize<'a>(
|
||||
_ => {
|
||||
// Keep all variable declarations at this level
|
||||
// and always keep the last return value
|
||||
let keep = matches!(stmt, Stmt::Let(_, _, _)) || i == num_statements - 1;
|
||||
let keep = match stmt {
|
||||
Stmt::Let(_, _, _) | Stmt::Import(_, _, _) => true,
|
||||
_ => i == num_statements - 1,
|
||||
};
|
||||
optimize_stmt(stmt, &mut state, keep)
|
||||
}
|
||||
}
|
||||
@ -708,11 +711,16 @@ pub fn optimize_into_ast(
|
||||
#[cfg(feature = "no_optimize")]
|
||||
const level: OptimizationLevel = OptimizationLevel::None;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let fn_lib: Vec<_> = functions
|
||||
.iter()
|
||||
.map(|fn_def| (fn_def.name.as_str(), fn_def.params.len()))
|
||||
.collect();
|
||||
|
||||
#[cfg(feature = "no_function")]
|
||||
const fn_lib: &[(&str, usize)] = &[];
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let lib = FunctionsLib::from_vec(
|
||||
functions
|
||||
.iter()
|
||||
@ -742,16 +750,16 @@ pub fn optimize_into_ast(
|
||||
.collect(),
|
||||
);
|
||||
|
||||
AST(
|
||||
#[cfg(feature = "no_function")]
|
||||
let lib: FunctionsLib = Default::default();
|
||||
|
||||
AST::new(
|
||||
match level {
|
||||
OptimizationLevel::None => statements,
|
||||
OptimizationLevel::Simple | OptimizationLevel::Full => {
|
||||
optimize(statements, engine, &scope, &fn_lib, level)
|
||||
}
|
||||
},
|
||||
#[cfg(feature = "sync")]
|
||||
Arc::new(lib),
|
||||
#[cfg(not(feature = "sync"))]
|
||||
Rc::new(lib),
|
||||
lib,
|
||||
)
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
#![cfg(not(feature = "no_index"))]
|
||||
|
||||
use super::{reg_binary, reg_binary_mut, reg_trinary_mut, reg_unary_mut};
|
||||
|
||||
use crate::any::{Dynamic, Variant};
|
||||
|
@ -1,3 +1,5 @@
|
||||
#![cfg(not(feature = "no_object"))]
|
||||
|
||||
use super::{reg_binary, reg_binary_mut, reg_unary_mut};
|
||||
|
||||
use crate::any::Dynamic;
|
||||
|
@ -47,6 +47,7 @@ pub trait Package {
|
||||
}
|
||||
|
||||
/// Type to store all functions in the package.
|
||||
#[derive(Default)]
|
||||
pub struct PackageStore {
|
||||
/// All functions, keyed by a hash created from the function name and parameter types.
|
||||
pub functions: HashMap<u64, Box<FnAny>>,
|
||||
@ -58,10 +59,7 @@ pub struct PackageStore {
|
||||
impl PackageStore {
|
||||
/// Create a new `PackageStore`.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
functions: HashMap::new(),
|
||||
type_iterators: HashMap::new(),
|
||||
}
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,16 @@
|
||||
use super::{reg_binary, reg_binary_mut, reg_none, reg_unary, reg_unary_mut};
|
||||
|
||||
use crate::def_package;
|
||||
use crate::engine::{Array, Map, FUNC_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT};
|
||||
use crate::engine::{FUNC_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT};
|
||||
use crate::fn_register::map_dynamic as map;
|
||||
use crate::parser::INT;
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
use crate::engine::Array;
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
use crate::engine::Map;
|
||||
|
||||
use crate::stdlib::{
|
||||
fmt::{Debug, Display},
|
||||
format,
|
||||
@ -18,6 +24,7 @@ fn to_debug<T: Debug>(x: &mut T) -> String {
|
||||
fn to_string<T: Display>(x: &mut T) -> String {
|
||||
format!("{}", x)
|
||||
}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
fn format_map(x: &mut Map) -> String {
|
||||
format!("#{:?}", x)
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
use super::{reg_binary, reg_binary_mut, reg_trinary_mut, reg_unary_mut};
|
||||
|
||||
use crate::def_package;
|
||||
use crate::engine::Array;
|
||||
use crate::fn_register::map_dynamic as map;
|
||||
use crate::parser::INT;
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
use crate::engine::Array;
|
||||
|
||||
use crate::stdlib::{
|
||||
fmt::Display,
|
||||
format,
|
||||
|
439
src/parser.rs
439
src/parser.rs
File diff suppressed because it is too large
Load Diff
@ -29,7 +29,7 @@ pub enum EvalAltResult {
|
||||
///
|
||||
/// Never appears under the `no_std` feature.
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
ErrorReadingScriptFile(PathBuf, std::io::Error),
|
||||
ErrorReadingScriptFile(PathBuf, Position, std::io::Error),
|
||||
|
||||
/// Call to an unknown function. Wrapped value is the name of the function.
|
||||
ErrorFunctionNotFound(String, Position),
|
||||
@ -47,12 +47,14 @@ pub enum EvalAltResult {
|
||||
/// String indexing out-of-bounds.
|
||||
/// Wrapped values are the current number of characters in the string and the index number.
|
||||
ErrorStringBounds(usize, INT, Position),
|
||||
/// Trying to index into a type that is not an array, an object map, or a string.
|
||||
/// Trying to index into a type that is not an array, an object map, or a string, and has no indexer function defined.
|
||||
ErrorIndexingType(String, Position),
|
||||
/// Trying to index into an array or string with an index that is not `i64`.
|
||||
ErrorNumericIndexExpr(Position),
|
||||
/// Trying to index into a map with an index that is not `String`.
|
||||
ErrorStringIndexExpr(Position),
|
||||
/// Trying to import with an expression that is not `String`.
|
||||
ErrorImportExpr(Position),
|
||||
/// Invalid arguments for `in` operator.
|
||||
ErrorInExpr(Position),
|
||||
/// The guard expression in an `if` or `while` statement does not return a boolean value.
|
||||
@ -61,6 +63,8 @@ pub enum EvalAltResult {
|
||||
ErrorFor(Position),
|
||||
/// Usage of an unknown variable. Wrapped value is the name of the variable.
|
||||
ErrorVariableNotFound(String, Position),
|
||||
/// Usage of an unknown module. Wrapped value is the name of the module.
|
||||
ErrorModuleNotFound(String, Position),
|
||||
/// Assignment to an inappropriate LHS (left-hand-side) expression.
|
||||
ErrorAssignmentToUnknownLHS(Position),
|
||||
/// Assignment to a constant variable.
|
||||
@ -90,7 +94,7 @@ impl EvalAltResult {
|
||||
pub(crate) fn desc(&self) -> &str {
|
||||
match self {
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
Self::ErrorReadingScriptFile(_, _) => "Cannot read from script file",
|
||||
Self::ErrorReadingScriptFile(_, _, _) => "Cannot read from script file",
|
||||
|
||||
Self::ErrorParsing(p) => p.desc(),
|
||||
Self::ErrorFunctionNotFound(_, _) => "Function not found",
|
||||
@ -104,8 +108,9 @@ impl EvalAltResult {
|
||||
}
|
||||
Self::ErrorStringIndexExpr(_) => "Indexing into an object map expects a string index",
|
||||
Self::ErrorIndexingType(_, _) => {
|
||||
"Indexing can only be performed on an array, an object map, or a string"
|
||||
"Indexing can only be performed on an array, an object map, a string, or a type with an indexer function defined"
|
||||
}
|
||||
Self::ErrorImportExpr(_) => "Importing a module expects a string path",
|
||||
Self::ErrorArrayBounds(_, index, _) if *index < 0 => {
|
||||
"Array access expects non-negative index"
|
||||
}
|
||||
@ -119,6 +124,7 @@ impl EvalAltResult {
|
||||
Self::ErrorLogicGuard(_) => "Boolean value expected",
|
||||
Self::ErrorFor(_) => "For loop expects an array, object map, or range",
|
||||
Self::ErrorVariableNotFound(_, _) => "Variable not found",
|
||||
Self::ErrorModuleNotFound(_, _) => "module not found",
|
||||
Self::ErrorAssignmentToUnknownLHS(_) => {
|
||||
"Assignment to an unsupported left-hand side expression"
|
||||
}
|
||||
@ -144,20 +150,26 @@ impl fmt::Display for EvalAltResult {
|
||||
|
||||
match self {
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
Self::ErrorReadingScriptFile(path, err) => {
|
||||
Self::ErrorReadingScriptFile(path, pos, err) if pos.is_none() => {
|
||||
write!(f, "{} '{}': {}", desc, path.display(), err)
|
||||
}
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
Self::ErrorReadingScriptFile(path, pos, err) => {
|
||||
write!(f, "{} '{}': {} ({})", desc, path.display(), err, pos)
|
||||
}
|
||||
|
||||
Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p),
|
||||
|
||||
Self::ErrorFunctionNotFound(s, pos) | Self::ErrorVariableNotFound(s, pos) => {
|
||||
write!(f, "{}: '{}' ({})", desc, s, pos)
|
||||
}
|
||||
Self::ErrorFunctionNotFound(s, pos)
|
||||
| Self::ErrorVariableNotFound(s, pos)
|
||||
| Self::ErrorModuleNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos),
|
||||
|
||||
Self::ErrorDotExpr(s, pos) if !s.is_empty() => write!(f, "{} {} ({})", desc, s, pos),
|
||||
|
||||
Self::ErrorIndexingType(_, pos)
|
||||
| Self::ErrorNumericIndexExpr(pos)
|
||||
| Self::ErrorStringIndexExpr(pos)
|
||||
| Self::ErrorImportExpr(pos)
|
||||
| Self::ErrorLogicGuard(pos)
|
||||
| Self::ErrorFor(pos)
|
||||
| Self::ErrorAssignmentToUnknownLHS(pos)
|
||||
@ -253,7 +265,7 @@ impl EvalAltResult {
|
||||
pub fn position(&self) -> Position {
|
||||
match self {
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
Self::ErrorReadingScriptFile(_, _) => Position::none(),
|
||||
Self::ErrorReadingScriptFile(_, pos, _) => *pos,
|
||||
|
||||
Self::ErrorParsing(err) => err.position(),
|
||||
|
||||
@ -266,9 +278,11 @@ impl EvalAltResult {
|
||||
| Self::ErrorIndexingType(_, pos)
|
||||
| Self::ErrorNumericIndexExpr(pos)
|
||||
| Self::ErrorStringIndexExpr(pos)
|
||||
| Self::ErrorImportExpr(pos)
|
||||
| Self::ErrorLogicGuard(pos)
|
||||
| Self::ErrorFor(pos)
|
||||
| Self::ErrorVariableNotFound(_, pos)
|
||||
| Self::ErrorModuleNotFound(_, pos)
|
||||
| Self::ErrorAssignmentToUnknownLHS(pos)
|
||||
| Self::ErrorAssignmentToConstant(_, pos)
|
||||
| Self::ErrorMismatchOutputType(_, pos)
|
||||
@ -287,7 +301,7 @@ impl EvalAltResult {
|
||||
pub(crate) fn set_position(mut err: Box<Self>, new_position: Position) -> Box<Self> {
|
||||
match err.as_mut() {
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
Self::ErrorReadingScriptFile(_, _) => (),
|
||||
Self::ErrorReadingScriptFile(_, pos, _) => *pos = new_position,
|
||||
|
||||
Self::ErrorParsing(err) => err.1 = new_position,
|
||||
|
||||
@ -300,9 +314,11 @@ impl EvalAltResult {
|
||||
| Self::ErrorIndexingType(_, pos)
|
||||
| Self::ErrorNumericIndexExpr(pos)
|
||||
| Self::ErrorStringIndexExpr(pos)
|
||||
| Self::ErrorImportExpr(pos)
|
||||
| Self::ErrorLogicGuard(pos)
|
||||
| Self::ErrorFor(pos)
|
||||
| Self::ErrorVariableNotFound(_, pos)
|
||||
| Self::ErrorModuleNotFound(_, pos)
|
||||
| Self::ErrorAssignmentToUnknownLHS(pos)
|
||||
| Self::ErrorAssignmentToConstant(_, pos)
|
||||
| Self::ErrorMismatchOutputType(_, pos)
|
||||
|
90
src/scope.rs
90
src/scope.rs
@ -1,10 +1,13 @@
|
||||
//! Module that defines the `Scope` type representing a function call-stack scope.
|
||||
|
||||
use crate::any::{Dynamic, Variant};
|
||||
use crate::any::{Dynamic, Union, Variant};
|
||||
use crate::parser::{map_dynamic_to_expr, Expr};
|
||||
use crate::token::Position;
|
||||
|
||||
use crate::stdlib::{borrow::Cow, boxed::Box, iter, vec::Vec};
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
use crate::module::Module;
|
||||
|
||||
use crate::stdlib::{borrow::Cow, boxed::Box, iter, vec, vec::Vec};
|
||||
|
||||
/// Type of an entry in the Scope.
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
|
||||
@ -13,6 +16,9 @@ pub enum EntryType {
|
||||
Normal,
|
||||
/// Immutable constant value.
|
||||
Constant,
|
||||
/// Name of a module, allowing member access with the :: operator.
|
||||
/// This is for internal use only.
|
||||
Module,
|
||||
}
|
||||
|
||||
/// An entry in the Scope.
|
||||
@ -56,7 +62,7 @@ pub struct Entry<'a> {
|
||||
/// allowing for automatic _shadowing_.
|
||||
///
|
||||
/// Currently, `Scope` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Scope<'a>(Vec<Entry<'a>>);
|
||||
|
||||
impl<'a> Scope<'a> {
|
||||
@ -73,7 +79,7 @@ impl<'a> Scope<'a> {
|
||||
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
|
||||
/// ```
|
||||
pub fn new() -> Self {
|
||||
Self(Vec::new())
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Empty the Scope.
|
||||
@ -165,6 +171,19 @@ impl<'a> Scope<'a> {
|
||||
self.push_dynamic_value(name, EntryType::Normal, value, false);
|
||||
}
|
||||
|
||||
/// Add (push) a new module to the Scope.
|
||||
///
|
||||
/// Modules are used for accessing member variables, functions and plugins under a namespace.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub fn push_module<K: Into<Cow<'a, str>>>(&mut self, name: K, value: Module) {
|
||||
self.push_dynamic_value(
|
||||
name,
|
||||
EntryType::Module,
|
||||
Dynamic(Union::Module(Box::new(value))),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
/// Add (push) a new constant to the Scope.
|
||||
///
|
||||
/// Constants are immutable and cannot be assigned to. Their values never change.
|
||||
@ -279,26 +298,61 @@ impl<'a> Scope<'a> {
|
||||
self.0
|
||||
.iter()
|
||||
.rev() // Always search a Scope in reverse order
|
||||
.any(|Entry { name: key, .. }| name == key)
|
||||
.any(|Entry { name: key, typ, .. }| match typ {
|
||||
EntryType::Normal | EntryType::Constant => name == key,
|
||||
EntryType::Module => false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Find an entry in the Scope, starting from the last.
|
||||
pub(crate) fn get(&self, name: &str) -> Option<(usize, EntryType)> {
|
||||
///
|
||||
/// modules are ignored.
|
||||
pub(crate) fn get_index(&self, name: &str) -> Option<(usize, EntryType)> {
|
||||
self.0
|
||||
.iter()
|
||||
.enumerate()
|
||||
.rev() // Always search a Scope in reverse order
|
||||
.find_map(|(index, Entry { name: key, typ, .. })| {
|
||||
.find_map(|(index, Entry { name: key, typ, .. })| match typ {
|
||||
EntryType::Normal | EntryType::Constant => {
|
||||
if name == key {
|
||||
Some((index, *typ))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
EntryType::Module => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Find a module in the Scope, starting from the last.
|
||||
pub(crate) fn get_module_index(&self, name: &str) -> Option<usize> {
|
||||
self.0
|
||||
.iter()
|
||||
.enumerate()
|
||||
.rev() // Always search a Scope in reverse order
|
||||
.find_map(|(index, Entry { name: key, typ, .. })| match typ {
|
||||
EntryType::Module => {
|
||||
if name == key {
|
||||
Some(index)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
EntryType::Normal | EntryType::Constant => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Find a module in the Scope, starting from the last entry.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub fn find_module(&mut self, name: &str) -> Option<&mut Module> {
|
||||
let index = self.get_module_index(name)?;
|
||||
self.get_mut(index).0.downcast_mut::<Module>()
|
||||
}
|
||||
|
||||
/// Get the value of an entry in the Scope, starting from the last.
|
||||
///
|
||||
/// modules are ignored.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
@ -313,7 +367,10 @@ impl<'a> Scope<'a> {
|
||||
self.0
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|Entry { name: key, .. }| name == key)
|
||||
.find(|Entry { name: key, typ, .. }| match typ {
|
||||
EntryType::Normal | EntryType::Constant => name == key,
|
||||
EntryType::Module => false,
|
||||
})
|
||||
.and_then(|Entry { value, .. }| value.downcast_ref::<T>().cloned())
|
||||
}
|
||||
|
||||
@ -339,12 +396,14 @@ impl<'a> Scope<'a> {
|
||||
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 0);
|
||||
/// ```
|
||||
pub fn set_value<T: Variant + Clone>(&mut self, name: &'a str, value: T) {
|
||||
match self.get(name) {
|
||||
match self.get_index(name) {
|
||||
None => self.push(name, value),
|
||||
Some((_, EntryType::Constant)) => panic!("variable {} is constant", name),
|
||||
Some((index, EntryType::Normal)) => {
|
||||
self.0.get_mut(index).unwrap().value = Dynamic::from(value)
|
||||
}
|
||||
None => self.push(name, value),
|
||||
// modules cannot be modified
|
||||
Some((_, EntryType::Module)) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -361,18 +420,17 @@ impl<'a> Scope<'a> {
|
||||
(&mut entry.value, entry.typ)
|
||||
}
|
||||
|
||||
/// Get an iterator to entries in the Scope.
|
||||
pub(crate) fn into_iter(self) -> impl Iterator<Item = Entry<'a>> {
|
||||
self.0.into_iter()
|
||||
}
|
||||
|
||||
/// Get an iterator to entries in the Scope.
|
||||
pub(crate) fn iter(&self) -> impl Iterator<Item = &Entry> {
|
||||
self.0.iter().rev() // Always search a Scope in reverse order
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Scope<'_> {
|
||||
fn default() -> Self {
|
||||
Scope::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K: Into<Cow<'a, str>>> iter::Extend<(K, EntryType, Dynamic)> for Scope<'a> {
|
||||
fn extend<T: IntoIterator<Item = (K, EntryType, Dynamic)>>(&mut self, iter: T) {
|
||||
self.0
|
||||
|
39
src/token.rs
39
src/token.rs
@ -122,7 +122,7 @@ impl fmt::Display for Position {
|
||||
|
||||
impl fmt::Debug for Position {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "({}:{})", self.line, self.pos)
|
||||
write!(f, "{}:{}", self.line, self.pos)
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,9 +153,9 @@ pub enum Token {
|
||||
RightShift,
|
||||
SemiColon,
|
||||
Colon,
|
||||
DoubleColon,
|
||||
Comma,
|
||||
Period,
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
MapStart,
|
||||
Equals,
|
||||
True,
|
||||
@ -180,7 +180,6 @@ pub enum Token {
|
||||
XOr,
|
||||
Ampersand,
|
||||
And,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Fn,
|
||||
Continue,
|
||||
Break,
|
||||
@ -197,6 +196,9 @@ pub enum Token {
|
||||
XOrAssign,
|
||||
ModuloAssign,
|
||||
PowerOfAssign,
|
||||
Import,
|
||||
Export,
|
||||
As,
|
||||
LexError(Box<LexError>),
|
||||
EOF,
|
||||
}
|
||||
@ -230,9 +232,9 @@ impl Token {
|
||||
Divide => "/",
|
||||
SemiColon => ";",
|
||||
Colon => ":",
|
||||
DoubleColon => "::",
|
||||
Comma => ",",
|
||||
Period => ".",
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
MapStart => "#{",
|
||||
Equals => "=",
|
||||
True => "true",
|
||||
@ -243,6 +245,8 @@ impl Token {
|
||||
Else => "else",
|
||||
While => "while",
|
||||
Loop => "loop",
|
||||
For => "for",
|
||||
In => "in",
|
||||
LessThan => "<",
|
||||
GreaterThan => ">",
|
||||
Bang => "!",
|
||||
@ -254,7 +258,6 @@ impl Token {
|
||||
Or => "||",
|
||||
Ampersand => "&",
|
||||
And => "&&",
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Fn => "fn",
|
||||
Continue => "continue",
|
||||
Break => "break",
|
||||
@ -276,8 +279,9 @@ impl Token {
|
||||
ModuloAssign => "%=",
|
||||
PowerOf => "~",
|
||||
PowerOfAssign => "~=",
|
||||
For => "for",
|
||||
In => "in",
|
||||
Import => "import",
|
||||
Export => "export",
|
||||
As => "as",
|
||||
EOF => "{EOF}",
|
||||
_ => panic!("operator should be match in outer scope"),
|
||||
})
|
||||
@ -406,6 +410,12 @@ impl Token {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Token> for String {
|
||||
fn from(token: Token) -> Self {
|
||||
token.syntax().into()
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator on a `Token` stream.
|
||||
pub struct TokenIterator<'a> {
|
||||
/// Can the next token be a unary operator?
|
||||
@ -741,6 +751,13 @@ impl<'a> TokenIterator<'a> {
|
||||
"for" => Token::For,
|
||||
"in" => Token::In,
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
"import" => Token::Import,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
"export" => Token::Export,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
"as" => Token::As,
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
"fn" => Token::Fn,
|
||||
|
||||
@ -874,7 +891,6 @@ impl<'a> TokenIterator<'a> {
|
||||
('/', _) => return Some((Token::Divide, pos)),
|
||||
|
||||
(';', _) => return Some((Token::SemiColon, pos)),
|
||||
(':', _) => return Some((Token::Colon, pos)),
|
||||
(',', _) => return Some((Token::Comma, pos)),
|
||||
('.', _) => return Some((Token::Period, pos)),
|
||||
|
||||
@ -896,6 +912,13 @@ impl<'a> TokenIterator<'a> {
|
||||
}
|
||||
('=', _) => return Some((Token::Equals, pos)),
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
(':', ':') => {
|
||||
self.eat_next();
|
||||
return Some((Token::DoubleColon, pos));
|
||||
}
|
||||
(':', _) => return Some((Token::Colon, pos)),
|
||||
|
||||
('<', '=') => {
|
||||
self.eat_next();
|
||||
return Some((Token::LessThanEqualsTo, pos));
|
||||
|
128
src/utils.rs
Normal file
128
src/utils.rs
Normal file
@ -0,0 +1,128 @@
|
||||
//! Module containing various utility types and functions.
|
||||
|
||||
use crate::stdlib::{
|
||||
any::TypeId,
|
||||
fmt,
|
||||
hash::{Hash, Hasher},
|
||||
mem,
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
use crate::stdlib::collections::hash_map::DefaultHasher;
|
||||
|
||||
#[cfg(feature = "no_std")]
|
||||
use ahash::AHasher;
|
||||
|
||||
/// Calculate a `u64` hash key from a function name and parameter types.
|
||||
///
|
||||
/// Parameter types are passed in via `TypeId` values from an iterator
|
||||
/// which can come from any source.
|
||||
pub fn calc_fn_spec(fn_name: &str, params: impl Iterator<Item = TypeId>) -> u64 {
|
||||
#[cfg(feature = "no_std")]
|
||||
let mut s: AHasher = Default::default();
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
let mut s = DefaultHasher::new();
|
||||
|
||||
s.write(fn_name.as_bytes());
|
||||
params.for_each(|t| t.hash(&mut s));
|
||||
s.finish()
|
||||
}
|
||||
|
||||
/// Calculate a `u64` hash key from a function name and number of parameters (without regard to types).
|
||||
pub(crate) fn calc_fn_def(fn_name: &str, num_params: usize) -> u64 {
|
||||
#[cfg(feature = "no_std")]
|
||||
let mut s: AHasher = Default::default();
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
let mut s = DefaultHasher::new();
|
||||
|
||||
s.write(fn_name.as_bytes());
|
||||
s.write_usize(num_params);
|
||||
s.finish()
|
||||
}
|
||||
|
||||
/// A type to hold a number of values in static storage for speed, and any spill-overs in a `Vec`.
|
||||
///
|
||||
/// This is essentially a knock-off of the [`staticvec`](https://crates.io/crates/staticvec) crate.
|
||||
/// This simplified implementation here is to avoid pulling in another crate.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct StaticVec<T: Default + Clone> {
|
||||
/// Total number of values held.
|
||||
len: usize,
|
||||
/// Static storage. 4 slots should be enough for most cases - i.e. four levels of indirection.
|
||||
list: [T; 4],
|
||||
/// Dynamic storage. For spill-overs.
|
||||
more: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T: Default + Clone> StaticVec<T> {
|
||||
/// Create a new `StaticVec`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
/// Push a new value to the end of this `StaticVec`.
|
||||
pub fn push<X: Into<T>>(&mut self, value: X) {
|
||||
if self.len >= self.list.len() {
|
||||
self.more.push(value.into());
|
||||
} else {
|
||||
self.list[self.len] = value.into();
|
||||
}
|
||||
self.len += 1;
|
||||
}
|
||||
/// Pop a value from the end of this `StaticVec`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the `StaticVec` is empty.
|
||||
pub fn pop(&mut self) -> T {
|
||||
let result = if self.len <= 0 {
|
||||
panic!("nothing to pop!")
|
||||
} else if self.len <= self.list.len() {
|
||||
mem::take(self.list.get_mut(self.len - 1).unwrap())
|
||||
} else {
|
||||
self.more.pop().unwrap()
|
||||
};
|
||||
|
||||
self.len -= 1;
|
||||
|
||||
result
|
||||
}
|
||||
/// Get the number of items in this `StaticVec`.
|
||||
pub fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
/// Get an item at a particular index.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the index is out of bounds.
|
||||
pub fn get(&self, index: usize) -> &T {
|
||||
if index >= self.len {
|
||||
panic!("index OOB in StaticVec");
|
||||
}
|
||||
|
||||
if index < self.list.len() {
|
||||
self.list.get(index).unwrap()
|
||||
} else {
|
||||
self.more.get(index - self.list.len()).unwrap()
|
||||
}
|
||||
}
|
||||
/// Get an iterator to entries in the `StaticVec`.
|
||||
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
||||
let num = if self.len >= self.list.len() {
|
||||
self.list.len()
|
||||
} else {
|
||||
self.len
|
||||
};
|
||||
|
||||
self.list[..num].iter().chain(self.more.iter())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default + Clone + fmt::Debug> fmt::Debug for StaticVec<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "[ ")?;
|
||||
self.iter().try_for_each(|v| write!(f, "{:?}, ", v))?;
|
||||
write!(f, "]")
|
||||
}
|
||||
}
|
@ -7,6 +7,8 @@ fn test_get_set() -> Result<(), Box<EvalAltResult>> {
|
||||
#[derive(Clone)]
|
||||
struct TestStruct {
|
||||
x: INT,
|
||||
y: INT,
|
||||
array: Vec<INT>,
|
||||
}
|
||||
|
||||
impl TestStruct {
|
||||
@ -18,8 +20,16 @@ fn test_get_set() -> Result<(), Box<EvalAltResult>> {
|
||||
self.x = new_x;
|
||||
}
|
||||
|
||||
fn get_y(&mut self) -> INT {
|
||||
self.y
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
TestStruct { x: 1 }
|
||||
TestStruct {
|
||||
x: 1,
|
||||
y: 0,
|
||||
array: vec![1, 2, 3, 4, 5],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,9 +38,19 @@ fn test_get_set() -> Result<(), Box<EvalAltResult>> {
|
||||
engine.register_type::<TestStruct>();
|
||||
|
||||
engine.register_get_set("x", TestStruct::get_x, TestStruct::set_x);
|
||||
engine.register_get("y", TestStruct::get_y);
|
||||
engine.register_fn("add", |value: &mut INT| *value += 41);
|
||||
engine.register_fn("new_ts", TestStruct::new);
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
engine.register_indexer(|value: &mut TestStruct, index: INT| value.array[index as usize]);
|
||||
|
||||
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x = 500; a.x")?, 500);
|
||||
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x.add(); a.x")?, 42);
|
||||
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.y.add(); a.y")?, 0);
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
assert_eq!(engine.eval::<INT>("let a = new_ts(); a[3]")?, 4);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
83
tests/modules.rs
Normal file
83
tests/modules.rs
Normal file
@ -0,0 +1,83 @@
|
||||
#![cfg(not(feature = "no_module"))]
|
||||
use rhai::{module_resolvers, Engine, EvalAltResult, Module, Scope, INT};
|
||||
|
||||
#[test]
|
||||
fn test_module() {
|
||||
let mut module = Module::new();
|
||||
module.set_var("answer", 42 as INT);
|
||||
|
||||
assert!(module.contains_var("answer"));
|
||||
assert_eq!(module.get_var_value::<INT>("answer").unwrap(), 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_module_sub_module() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut module = Module::new();
|
||||
|
||||
let mut sub_module = Module::new();
|
||||
|
||||
let mut sub_module2 = Module::new();
|
||||
sub_module2.set_var("answer", 41 as INT);
|
||||
let hash = sub_module2.set_fn_1("inc", |x: INT| Ok(x + 1));
|
||||
|
||||
sub_module.set_sub_module("universe", sub_module2);
|
||||
module.set_sub_module("life", sub_module);
|
||||
|
||||
assert!(module.contains_sub_module("life"));
|
||||
let m = module.get_sub_module("life").unwrap();
|
||||
|
||||
assert!(m.contains_sub_module("universe"));
|
||||
let m2 = m.get_sub_module("universe").unwrap();
|
||||
|
||||
assert!(m2.contains_var("answer"));
|
||||
assert!(m2.contains_fn(hash));
|
||||
|
||||
assert_eq!(m2.get_var_value::<INT>("answer").unwrap(), 41);
|
||||
|
||||
let mut engine = Engine::new();
|
||||
let mut scope = Scope::new();
|
||||
|
||||
scope.push_module("question", module);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval_expression_with_scope::<INT>(
|
||||
&mut scope,
|
||||
"question::life::universe::answer + 1"
|
||||
)?,
|
||||
42
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval_expression_with_scope::<INT>(
|
||||
&mut scope,
|
||||
"question::life::universe::inc(question::life::universe::answer)"
|
||||
)?,
|
||||
42
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut resolver = module_resolvers::StaticModuleResolver::new();
|
||||
|
||||
let mut module = Module::new();
|
||||
module.set_var("answer", 42 as INT);
|
||||
|
||||
resolver.insert("hello".to_string(), module);
|
||||
|
||||
let mut engine = Engine::new();
|
||||
engine.set_module_resolver(Some(resolver));
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
import "hello" as h;
|
||||
h::answer
|
||||
"#
|
||||
)?,
|
||||
42
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue
Block a user