Merge branch 'master' into plugins

This commit is contained in:
Stephen Chung 2020-05-06 20:00:02 +08:00
commit fa4d391e4b
36 changed files with 2302 additions and 540 deletions

View File

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

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

View File

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

View File

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

View File

@ -1,2 +1,4 @@
print("x should be 78:");
let x = 78;
print(x);

View File

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

View File

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

View File

@ -4,4 +4,6 @@ fn bob() {
return 3;
}
print(bob()); // should print 3
print("bob() should be 3:");
print(bob());

View File

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

View File

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

View File

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

View File

@ -1 +1,3 @@
print(34 + 12); // should be 46
print("The result should be 46:");
print(34 + 12);

View File

@ -1,2 +1,4 @@
print("The result should be 182:");
let x = 12 + 34 * 5;
print(x); // should be 182
print(x);

View File

@ -1,2 +1,4 @@
print("The result should be 230:");
let x = (12 + 34) * 5;
print(x); // should be 230
print(x);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,5 @@
#![cfg(not(feature = "no_object"))]
use super::{reg_binary, reg_binary_mut, reg_unary_mut};
use crate::any::Dynamic;

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -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
View 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, "]")
}
}

View File

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