Merge pull request #152 from schungx/master

Module lookup/function calls efficiency, and general speed up
This commit is contained in:
Stephen Chung 2020-05-14 21:24:00 +08:00 committed by GitHub
commit 0513b68fef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 3347 additions and 2504 deletions

172
README.md
View File

@ -20,10 +20,11 @@ Rhai's current features set:
* Freely pass variables/constants into a script via an external [`Scope`] * 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) * Fairly efficient (1 million iterations in 0.75 sec on my 5 year old laptop)
* Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app) * Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app)
* Relatively little `unsafe` code (yes there are some for performance reasons)
* [`no-std`](#optional-features) support * [`no-std`](#optional-features) support
* Support for [function overloading](#function-overloading) * [Function overloading](#function-overloading)
* Support for [operator overloading](#operator-overloading) * [Operator overloading](#operator-overloading)
* Support for loading external [modules] * [Modules]
* Compiled script is [optimized](#script-optimization) for repeat evaluations * Compiled script is [optimized](#script-optimization) for repeat evaluations
* Support for [minimal builds](#minimal-builds) by excluding unneeded language [features](#optional-features) * 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/) * Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/)
@ -126,7 +127,8 @@ Disable script-defined functions (`no_function`) only when the feature is not ne
[`Engine::new_raw`](#raw-engine) creates a _raw_ engine which does not register _any_ utility functions. [`Engine::new_raw`](#raw-engine) creates a _raw_ engine which does not register _any_ utility functions.
This makes the scripting language quite useless as even basic arithmetic operators are not supported. This makes the scripting language quite useless as even basic arithmetic operators are not supported.
Selectively include the necessary operators by loading specific [packages](#packages) while minimizing the code footprint. Selectively include the necessary functionalities by loading specific [packages](#packages) to minimize the footprint.
Packages are sharable (even across threads via the [`sync`] feature), so they only have to be created once.
Related Related
------- -------
@ -268,6 +270,7 @@ let ast = engine.compile_file("hello_world.rhai".into())?;
### Calling Rhai functions from Rust ### Calling Rhai functions from Rust
Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust via `call_fn`. Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust via `call_fn`.
Functions declared with `private` are hidden and cannot be called from Rust (see also [modules]).
```rust ```rust
// Define functions in a script. // Define functions in a script.
@ -287,6 +290,11 @@ let ast = engine.compile(true,
fn hello() { fn hello() {
42 42
} }
// this one is private and cannot be called by 'call_fn'
private hidden() {
throw "you shouldn't see me!";
}
")?; ")?;
// A custom scope can also contain any variables/constants available to the functions // A custom scope can also contain any variables/constants available to the functions
@ -300,11 +308,15 @@ let result: i64 = engine.call_fn(&mut scope, &ast, "hello", ( String::from("abc"
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// put arguments in a tuple // put arguments in a tuple
let result: i64 = engine.call_fn(&mut scope, &ast, "hello", (123_i64,) )? let result: i64 = engine.call_fn(&mut scope, &ast, "hello", (123_i64,) )?;
// ^^^^^^^^^^ tuple of one // ^^^^^^^^^^ tuple of one
let result: i64 = engine.call_fn(&mut scope, &ast, "hello", () )? let result: i64 = engine.call_fn(&mut scope, &ast, "hello", () )?;
// ^^ unit = tuple of zero // ^^ unit = tuple of zero
// The following call will return a function-not-found error because
// 'hidden' is declared with 'private'.
let result: () = engine.call_fn(&mut scope, &ast, "hidden", ())?;
``` ```
### Creating Rust anonymous functions from Rhai script ### Creating Rust anonymous functions from Rhai script
@ -361,7 +373,7 @@ Use `Engine::new_raw` to create a _raw_ `Engine`, in which _nothing_ is added, n
### Packages ### Packages
Rhai functional features are provided in different _packages_ that can be loaded via a call to `load_package`. Rhai functional features are provided in different _packages_ that can be loaded via a call to `load_package`.
Packages reside under `rhai::packages::*` and the trait `rhai::packages::Package` must be imported in order for Packages reside under `rhai::packages::*` and the trait `rhai::packages::Package` must be loaded in order for
packages to be used. packages to be used.
```rust ```rust
@ -372,7 +384,7 @@ use rhai::packages::CorePackage; // the 'core' package contains b
let mut engine = Engine::new_raw(); // create a 'raw' Engine let mut engine = Engine::new_raw(); // create a 'raw' Engine
let package = CorePackage::new(); // create a package - can be shared among multiple `Engine` instances let package = CorePackage::new(); // create a package - can be shared among multiple `Engine` instances
engine.load_package(package.get()); // load the package manually engine.load_package(package.get()); // load the package manually. 'get' returns a reference to the shared package
``` ```
The follow packages are available: The follow packages are available:
@ -391,6 +403,20 @@ The follow packages are available:
| `CorePackage` | Basic essentials | | | | `CorePackage` | Basic essentials | | |
| `StandardPackage` | Standard library | | | | `StandardPackage` | Standard library | | |
Packages typically contain Rust functions that are callable within a Rhai script.
All functions registered in a package is loaded under the _global namespace_ (i.e. they're available without module qualifiers).
Once a package is created (e.g. via `new`), it can be _shared_ (via `get`) among multiple instances of [`Engine`],
even across threads (if the [`sync`] feature is turned on).
Therefore, a package only has to be created _once_.
Packages are actually implemented as [modules], so they share a lot of behavior and characteristics.
The main difference is that a package loads under the _global_ namespace, while a module loads under its own
namespace alias specified in an `import` statement (see also [modules]).
A package is _static_ (i.e. pre-loaded into an [`Engine`]), while a module is _dynamic_ (i.e. loaded with
the `import` statement).
Custom packages can also be created. See the macro [`def_package!`](https://docs.rs/rhai/0.13.0/rhai/macro.def_package.html).
Evaluate expressions only Evaluate expressions only
------------------------- -------------------------
@ -752,11 +778,17 @@ println!("result: {}", result); // prints 42
let result: f64 = engine.eval("1.0 + 0.0"); // '+' operator for two floats not overloaded let result: f64 = engine.eval("1.0 + 0.0"); // '+' operator for two floats not overloaded
println!("result: {}", result); // prints 1.0 println!("result: {}", result); // prints 1.0
fn mixed_add(a: i64, b: f64) -> f64 { (a as f64) + b }
engine.register_fn("+", mixed_add); // register '+' operator for an integer and a float
let result: i64 = engine.eval("1 + 1.0"); // prints 2.0 (normally an error)
``` ```
Use operator overloading for custom types (described below) only. Be very careful when overloading built-in operators because Use operator overloading for custom types (described below) only.
script writers expect standard operators to behave in a consistent and predictable manner, and will be annoyed if a calculation Be very careful when overloading built-in operators because script writers expect standard operators to behave in a
for '+' turns into a subtraction, for example. consistent and predictable manner, and will be annoyed if a calculation for '`+`' turns into a subtraction, for example.
Operator overloading also impacts script optimization when using [`OptimizationLevel::Full`]. Operator overloading also impacts script optimization when using [`OptimizationLevel::Full`].
See the [relevant section](#script-optimization) for more details. See the [relevant section](#script-optimization) for more details.
@ -1318,7 +1350,7 @@ The following standard methods (defined in the [`MoreStringPackage`](#packages)
| `index_of` | character/sub-string to search for, start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found | | `index_of` | character/sub-string to search for, start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found |
| `sub_string` | start index, length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) | | `sub_string` | start index, length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) |
| `crop` | start index, length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) | | `crop` | start index, length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) |
| `replace` | target sub-string, replacement string | replaces a sub-string with another | | `replace` | target character/sub-string, replacement character/string | replaces a sub-string with another |
| `trim` | _none_ | trims the string of whitespace at the beginning and end | | `trim` | _none_ | trims the string of whitespace at the beginning and end |
### Examples ### Examples
@ -2038,32 +2070,62 @@ for entry in logbook.read().unwrap().iter() {
} }
``` ```
Using external modules Modules
---------------------- -------
[module]: #using-external-modules [module]: #modules
[modules]: #using-external-modules [modules]: #modules
Rhai allows organizing code (functions and variables) into _modules_. A module is a single script file Rhai allows organizing code (functions, both Rust-based or script-based, and variables) into _modules_.
with `export` statements that _exports_ certain global variables and functions as contents of the module. Modules can be disabled via the [`no_module`] feature.
Everything exported as part of a module is constant and read-only. ### Exporting variables and functions from modules
A _module_ is a single script (or pre-compiled `AST`) containing global variables and functions.
The `export` statement, which can only be at global level, exposes selected variables as members of a module.
Variables not exported are _private_ and invisible to the outside.
On the other hand, all functions are automatically exported, _unless_ it is explicitly opt-out with the `private` prefix.
Functions declared `private` are invisible to the outside.
Everything exported from a module is **constant** (**read-only**).
```rust
// This is a module script.
fn inc(x) { x + 1 } // script-defined function - default public
private fn foo() {} // private function - invisible to outside
let private = 123; // variable not exported - default invisible to outside
let x = 42; // this will be exported below
export x; // the variable 'x' is exported under its own name
export x as answer; // the variable 'x' is exported under the alias 'answer'
// another script can load this module and access 'x' as 'module::answer'
```
### Importing modules ### Importing modules
A module can be _imported_ via the `import` statement, and its members accessed via '`::`' similar to C++. A module can be _imported_ via the `import` statement, and its members are accessed via '`::`' similar to C++.
```rust ```rust
import "crypto" as crypto; // import the script file 'crypto.rhai' as a module import "crypto" as crypto; // import the script file 'crypto.rhai' as a module
crypto::encrypt(secret); // use functions defined under the module via '::' crypto::encrypt(secret); // use functions defined under the module via '::'
crypto::hash::sha256(key); // sub-modules are also supported
print(crypto::status); // module variables are constants print(crypto::status); // module variables are constants
crypto::hash::sha256(key); // sub-modules are also supported crypto::status = "off"; // <- runtime error - cannot modify a constant
``` ```
`import` statements are _scoped_, meaning that they are only accessible inside the scope that they're imported. `import` statements are _scoped_, meaning that they are only accessible inside the scope that they're imported.
They can appear anywhere a normal statement can be, but in the vast majority of cases `import` statements are
group at the beginning of a script. It is not advised to deviate from this common practice unless there is
a _Very Good Reason™_. Especially, do not place an `import` statement within a loop; doing so will repeatedly
re-load the same module during every iteration of the loop!
```rust ```rust
let mod = "crypto"; let mod = "crypto";
@ -2076,11 +2138,17 @@ if secured { // new block scope
crypto::encrypt(others); // <- this causes a run-time error because the 'crypto' module crypto::encrypt(others); // <- this causes a run-time error because the 'crypto' module
// is no longer available! // is no longer available!
for x in range(0, 1000) {
import "crypto" as c; // <- importing a module inside a loop is a Very Bad Idea
c.encrypt(something);
}
``` ```
### Creating custom modules from Rust ### Creating custom modules with Rust
To load a custom module into an [`Engine`], first create a `Module` type, add variables/functions into it, To load a custom module (written in Rust) 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 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. at the beginning of any script run.
@ -2105,6 +2173,56 @@ engine.eval_expression_with_scope::<i64>(&scope, "question::answer + 1")? == 42;
engine.eval_expression_with_scope::<i64>(&scope, "question::inc(question::answer)")? == 42; engine.eval_expression_with_scope::<i64>(&scope, "question::inc(question::answer)")? == 42;
``` ```
### Creating a module from an `AST`
It is easy to convert a pre-compiled `AST` into a module: just use `Module::eval_ast_as_new`.
Don't forget the `export` statement, otherwise there will be no variables exposed by the module
other than non-`private` functions (unless that's intentional).
```rust
use rhai::{Engine, Module};
let engine = Engine::new();
// Compile a script into an 'AST'
let ast = engine.compile(r#"
// Functions become module functions
fn calc(x) {
x + 1
}
fn add_len(x, y) {
x + y.len()
}
// Imported modules can become sub-modules
import "another module" as extra;
// Variables defined at global level can become module variables
const x = 123;
let foo = 41;
let hello;
// Variable values become constant module variable values
foo = calc(foo);
hello = "hello, " + foo + " worlds!";
// Finally, export the variables and modules
export
x as abc, // aliased variable name
foo,
hello,
extra as foobar; // export sub-module
"#)?;
// Convert the 'AST' into a module, using the 'Engine' to evaluate it first
let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
// 'module' now can be loaded into a custom 'Scope' for future use. It contains:
// - sub-module: 'foobar' (renamed from 'extra')
// - functions: 'calc', 'add_len'
// - variables: 'abc' (renamed from 'x'), 'foo', 'hello'
```
### Module resolvers ### Module resolvers
When encountering an `import` statement, Rhai attempts to _resolve_ the module based on the path string. When encountering an `import` statement, Rhai attempts to _resolve_ the module based on the path string.
@ -2114,10 +2232,10 @@ which simply loads a script file based on the path (with `.rhai` extension attac
Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace. Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace.
| Module Resolver | Description | | 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. | | `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.<br/>`FileModuleResolver::create_module()` loads a script file and returns a module. |
| `StaticModuleResolver` | Loads modules that are statically added. This can be used when the [`no_std`] feature is turned on. | | `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`: An [`Engine`]'s module resolver is set via a call to `set_module_resolver`:

View File

@ -1,5 +1,4 @@
use rhai::{packages::*, Engine, EvalAltResult, INT}; use rhai::{packages::*, Engine, EvalAltResult, INT};
use std::rc::Rc;
fn main() -> Result<(), Box<EvalAltResult>> { fn main() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new_raw(); let mut engine = Engine::new_raw();

View File

@ -1,12 +1,9 @@
use rhai::{Dynamic, Engine, EvalAltResult, Scope, AST, INT}; use rhai::{Dynamic, Engine, EvalAltResult, Scope, AST};
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
use rhai::OptimizationLevel; use rhai::OptimizationLevel;
use std::{ use std::io::{stdin, stdout, Write};
io::{stdin, stdout, Write},
iter,
};
fn print_error(input: &str, err: EvalAltResult) { fn print_error(input: &str, err: EvalAltResult) {
let lines: Vec<_> = input.trim().split('\n').collect(); let lines: Vec<_> = input.trim().split('\n').collect();

View File

@ -3,7 +3,7 @@ use rhai::{Engine, EvalAltResult};
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
use rhai::OptimizationLevel; use rhai::OptimizationLevel;
use std::{env, fs::File, io::Read, iter, process::exit}; use std::{env, fs::File, io::Read, process::exit};
fn eprint_error(input: &str, err: EvalAltResult) { fn eprint_error(input: &str, err: EvalAltResult) {
fn eprint_line(lines: &[&str], line: usize, pos: usize, err: &str) { fn eprint_line(lines: &[&str], line: usize, pos: usize, err: &str) {

View File

@ -3,8 +3,6 @@
const target = 30; const target = 30;
let now = timestamp();
fn fib(n) { fn fib(n) {
if n < 2 { if n < 2 {
n n
@ -15,8 +13,14 @@ fn fib(n) {
print("Ready... Go!"); print("Ready... Go!");
let now = timestamp();
let result = fib(target); let result = fib(target);
print("Finished. Run time = " + now.elapsed() + " seconds.");
print("Fibonacci number #" + target + " = " + result); print("Fibonacci number #" + target + " = " + result);
print("Finished. Run time = " + now.elapsed() + " seconds."); if result != 832_040 {
print("The answer is WRONG! Should be 832,040!");
}

View File

@ -27,3 +27,7 @@ for p in range(2, MAX_NUMBER_TO_CHECK) {
print("Total " + total_primes_found + " primes <= " + MAX_NUMBER_TO_CHECK); print("Total " + total_primes_found + " primes <= " + MAX_NUMBER_TO_CHECK);
print("Run time = " + now.elapsed() + " seconds."); print("Run time = " + now.elapsed() + " seconds.");
if total_primes_found != 9_592 {
print("The answer is WRONG! Should be 9,592!");
}

View File

@ -1,10 +1,11 @@
//! Helper module which defines the `Any` trait to to allow dynamic value handling. //! Helper module which defines the `Any` trait to to allow dynamic value handling.
use crate::parser::INT;
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
use crate::module::Module; use crate::module::Module;
use crate::parser::INT;
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT; use crate::parser::FLOAT;
@ -18,7 +19,7 @@ use crate::stdlib::{
any::{type_name, Any, TypeId}, any::{type_name, Any, TypeId},
boxed::Box, boxed::Box,
collections::HashMap, collections::HashMap,
fmt, fmt, mem, ptr,
string::String, string::String,
vec::Vec, vec::Vec,
}; };
@ -38,6 +39,9 @@ pub trait Variant: Any {
/// Convert this `Variant` trait object to `&mut dyn Any`. /// Convert this `Variant` trait object to `&mut dyn Any`.
fn as_mut_any(&mut self) -> &mut dyn Any; fn as_mut_any(&mut self) -> &mut dyn Any;
/// Convert this `Variant` trait object to an `Any` trait object.
fn as_box_any(self: Box<Self>) -> Box<dyn Any>;
/// Get the name of this type. /// Get the name of this type.
fn type_name(&self) -> &'static str; fn type_name(&self) -> &'static str;
@ -60,6 +64,9 @@ impl<T: Any + Clone> Variant for T {
fn as_mut_any(&mut self) -> &mut dyn Any { fn as_mut_any(&mut self) -> &mut dyn Any {
self as &mut dyn Any self as &mut dyn Any
} }
fn as_box_any(self: Box<Self>) -> Box<dyn Any> {
self as Box<dyn Any>
}
fn type_name(&self) -> &'static str { fn type_name(&self) -> &'static str {
type_name::<T>() type_name::<T>()
} }
@ -86,6 +93,9 @@ pub trait Variant: Any + Send + Sync {
/// Convert this `Variant` trait object to `&mut dyn Any`. /// Convert this `Variant` trait object to `&mut dyn Any`.
fn as_mut_any(&mut self) -> &mut dyn Any; fn as_mut_any(&mut self) -> &mut dyn Any;
/// Convert this `Variant` trait object to an `Any` trait object.
fn as_box_any(self: Box<Self>) -> Box<dyn Any>;
/// Get the name of this type. /// Get the name of this type.
fn type_name(&self) -> &'static str; fn type_name(&self) -> &'static str;
@ -108,6 +118,9 @@ impl<T: Any + Clone + Send + Sync> Variant for T {
fn as_mut_any(&mut self) -> &mut dyn Any { fn as_mut_any(&mut self) -> &mut dyn Any {
self as &mut dyn Any self as &mut dyn Any
} }
fn as_box_any(self: Box<Self>) -> Box<dyn Any> {
self as Box<dyn Any>
}
fn type_name(&self) -> &'static str { fn type_name(&self) -> &'static str {
type_name::<T>() type_name::<T>()
} }
@ -133,6 +146,8 @@ impl dyn Variant {
pub struct Dynamic(pub(crate) Union); pub struct Dynamic(pub(crate) Union);
/// Internal `Dynamic` representation. /// Internal `Dynamic` representation.
///
/// Most variants are boxed to reduce the size.
pub enum Union { pub enum Union {
Unit(()), Unit(()),
Bool(bool), Bool(bool),
@ -284,24 +299,15 @@ impl Default for Dynamic {
} }
} }
/// Cast a Boxed type into another type.
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))
}
} else {
// Return the consumed item for chaining.
Err(item)
}
}
impl Dynamic { impl Dynamic {
/// Create a `Dynamic` from any type. A `Dynamic` value is simply returned as is. /// Create a `Dynamic` from any type. A `Dynamic` value is simply returned as is.
/// ///
/// # Safety
///
/// This type uses some unsafe code, mainly for type casting.
///
/// # Notes
///
/// Beware that you need to pass in an `Array` type for it to be recognized as an `Array`. /// Beware that you need to pass in an `Array` type for it to be recognized as an `Array`.
/// A `Vec<T>` does not get automatically converted to an `Array`, but will be a generic /// A `Vec<T>` does not get automatically converted to an `Array`, but will be a generic
/// restricted trait object instead, because `Vec<T>` is not a supported standard type. /// restricted trait object instead, because `Vec<T>` is not a supported standard type.
@ -347,17 +353,17 @@ impl Dynamic {
let mut var = Box::new(value); let mut var = Box::new(value);
var = match cast_box::<_, Dynamic>(var) { var = match unsafe_cast_box::<_, Dynamic>(var) {
Ok(d) => return *d, Ok(d) => return *d,
Err(var) => var, Err(var) => var,
}; };
var = match cast_box::<_, String>(var) { var = match unsafe_cast_box::<_, String>(var) {
Ok(s) => return Self(Union::Str(s)), Ok(s) => return Self(Union::Str(s)),
Err(var) => var, Err(var) => var,
}; };
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
{ {
var = match cast_box::<_, Array>(var) { var = match unsafe_cast_box::<_, Array>(var) {
Ok(array) => return Self(Union::Array(array)), Ok(array) => return Self(Union::Array(array)),
Err(var) => var, Err(var) => var,
}; };
@ -365,7 +371,7 @@ impl Dynamic {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
{ {
var = match cast_box::<_, Map>(var) { var = match unsafe_cast_box::<_, Map>(var) {
Ok(map) => return Self(Union::Map(map)), Ok(map) => return Self(Union::Map(map)),
Err(var) => var, Err(var) => var,
} }
@ -388,26 +394,26 @@ impl Dynamic {
/// ///
/// assert_eq!(x.try_cast::<u32>().unwrap(), 42); /// assert_eq!(x.try_cast::<u32>().unwrap(), 42);
/// ``` /// ```
pub fn try_cast<T: Variant + Clone>(self) -> Option<T> { pub fn try_cast<T: Variant>(self) -> Option<T> {
if TypeId::of::<T>() == TypeId::of::<Dynamic>() { if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
return cast_box::<_, T>(Box::new(self)).ok().map(|v| *v); return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v);
} }
match self.0 { match self.0 {
Union::Unit(ref value) => (value as &dyn Any).downcast_ref::<T>().cloned(), Union::Unit(value) => unsafe_try_cast(value),
Union::Bool(ref value) => (value as &dyn Any).downcast_ref::<T>().cloned(), Union::Bool(value) => unsafe_try_cast(value),
Union::Str(value) => cast_box::<_, T>(value).ok().map(|v| *v), Union::Str(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
Union::Char(ref value) => (value as &dyn Any).downcast_ref::<T>().cloned(), Union::Char(value) => unsafe_try_cast(value),
Union::Int(ref value) => (value as &dyn Any).downcast_ref::<T>().cloned(), Union::Int(value) => unsafe_try_cast(value),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(ref value) => (value as &dyn Any).downcast_ref::<T>().cloned(), Union::Float(value) => unsafe_try_cast(value),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(value) => cast_box::<_, T>(value).ok().map(|v| *v), Union::Array(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(value) => cast_box::<_, T>(value).ok().map(|v| *v), Union::Map(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Union::Module(value) => cast_box::<_, T>(value).ok().map(|v| *v), Union::Module(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
Union::Variant(value) => value.as_any().downcast_ref::<T>().cloned(), Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(),
} }
} }
@ -431,24 +437,24 @@ impl Dynamic {
//self.try_cast::<T>().unwrap() //self.try_cast::<T>().unwrap()
if TypeId::of::<T>() == TypeId::of::<Dynamic>() { if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
return *cast_box::<_, T>(Box::new(self)).unwrap(); return *unsafe_cast_box::<_, T>(Box::new(self)).unwrap();
} }
match self.0 { match self.0 {
Union::Unit(ref value) => (value as &dyn Any).downcast_ref::<T>().unwrap().clone(), Union::Unit(value) => unsafe_try_cast(value).unwrap(),
Union::Bool(ref value) => (value as &dyn Any).downcast_ref::<T>().unwrap().clone(), Union::Bool(value) => unsafe_try_cast(value).unwrap(),
Union::Str(value) => *cast_box::<_, T>(value).unwrap(), Union::Str(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
Union::Char(ref value) => (value as &dyn Any).downcast_ref::<T>().unwrap().clone(), Union::Char(value) => unsafe_try_cast(value).unwrap(),
Union::Int(ref value) => (value as &dyn Any).downcast_ref::<T>().unwrap().clone(), Union::Int(value) => unsafe_try_cast(value).unwrap(),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(ref value) => (value as &dyn Any).downcast_ref::<T>().unwrap().clone(), Union::Float(value) => unsafe_try_cast(value).unwrap(),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(value) => *cast_box::<_, T>(value).unwrap(), Union::Array(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(value) => *cast_box::<_, T>(value).unwrap(), Union::Map(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Union::Module(value) => *cast_box::<_, T>(value).unwrap(), Union::Module(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
Union::Variant(value) => value.as_any().downcast_ref::<T>().unwrap().clone(), Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).unwrap(),
} }
} }

View File

@ -4,12 +4,16 @@ use crate::any::{Dynamic, Variant};
use crate::engine::{make_getter, make_setter, Engine, State, FUNC_INDEXER}; use crate::engine::{make_getter, make_setter, Engine, State, FUNC_INDEXER};
use crate::error::ParseError; use crate::error::ParseError;
use crate::fn_call::FuncArgs; use crate::fn_call::FuncArgs;
use crate::fn_native::{
IteratorCallback, ObjectGetCallback, ObjectIndexerCallback, ObjectSetCallback,
};
use crate::fn_register::RegisterFn; use crate::fn_register::RegisterFn;
use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::optimize::{optimize_into_ast, OptimizationLevel};
use crate::parser::{parse, parse_global_expr, AST}; use crate::parser::{parse, parse_global_expr, AST};
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::Scope; use crate::scope::Scope;
use crate::token::{lex, Position}; use crate::token::{lex, Position};
use crate::utils::StaticVec;
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::engine::Map; use crate::engine::Map;
@ -20,58 +24,11 @@ use crate::stdlib::{
collections::HashMap, collections::HashMap,
mem, mem,
string::{String, ToString}, string::{String, ToString},
vec::Vec,
}; };
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf}; use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf};
// Define callback function types
#[cfg(feature = "sync")]
pub trait ObjectGetCallback<T, U>: Fn(&mut T) -> U + Send + Sync + 'static {}
#[cfg(feature = "sync")]
impl<F: Fn(&mut T) -> U + Send + Sync + 'static, T, U> ObjectGetCallback<T, U> for F {}
#[cfg(not(feature = "sync"))]
pub trait ObjectGetCallback<T, U>: Fn(&mut T) -> U + 'static {}
#[cfg(not(feature = "sync"))]
impl<F: Fn(&mut T) -> U + 'static, T, U> ObjectGetCallback<T, U> for F {}
#[cfg(feature = "sync")]
pub trait ObjectSetCallback<T, U>: Fn(&mut T, U) + Send + Sync + 'static {}
#[cfg(feature = "sync")]
impl<F: Fn(&mut T, U) + Send + Sync + 'static, T, U> ObjectSetCallback<T, U> for F {}
#[cfg(not(feature = "sync"))]
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
{
}
#[cfg(feature = "sync")]
impl<F: Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + Send + Sync + 'static> IteratorCallback
for F
{
}
#[cfg(not(feature = "sync"))]
pub trait IteratorCallback: Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + 'static {}
#[cfg(not(feature = "sync"))]
impl<F: Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + 'static> IteratorCallback for F {}
/// Engine public API /// Engine public API
impl Engine { impl Engine {
/// Register a custom type for use with the `Engine`. /// Register a custom type for use with the `Engine`.
@ -168,7 +125,7 @@ impl Engine {
/// Register an iterator adapter for a type with the `Engine`. /// Register an iterator adapter for a type with the `Engine`.
/// This is an advanced feature. /// This is an advanced feature.
pub fn register_iterator<T: Variant + Clone, F: IteratorCallback>(&mut self, f: F) { pub fn register_iterator<T: Variant + Clone, F: IteratorCallback>(&mut self, f: F) {
self.type_iterators.insert(TypeId::of::<T>(), Box::new(f)); self.global_module.set_iter(TypeId::of::<T>(), Box::new(f));
} }
/// Register a getter function for a member of a registered type with the `Engine`. /// Register a getter function for a member of a registered type with the `Engine`.
@ -385,6 +342,7 @@ impl Engine {
} }
/// Compile a string into an `AST` using own scope, which can be used later for evaluation. /// Compile a string into an `AST` using own scope, which can be used later for evaluation.
///
/// The scope is useful for passing constants into the script for optimization /// The scope is useful for passing constants into the script for optimization
/// when using `OptimizationLevel::Full`. /// when using `OptimizationLevel::Full`.
/// ///
@ -422,18 +380,71 @@ impl Engine {
/// # } /// # }
/// ``` /// ```
pub fn compile_with_scope(&self, scope: &Scope, script: &str) -> Result<AST, Box<ParseError>> { pub fn compile_with_scope(&self, scope: &Scope, script: &str) -> Result<AST, Box<ParseError>> {
self.compile_with_scope_and_optimization_level(scope, script, self.optimization_level) self.compile_scripts_with_scope(scope, &[script])
} }
/// Compile a string into an `AST` using own scope at a specific optimization level. /// When passed a list of strings, first join the strings into one large script,
/// and then compile them into an `AST` using own scope, which can be used later for evaluation.
///
/// The scope is useful for passing constants into the script for optimization
/// when using `OptimizationLevel::Full`.
///
/// ## Note
///
/// All strings are simply parsed one after another with nothing inserted in between, not even
/// a newline or space.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # #[cfg(not(feature = "no_optimize"))]
/// # {
/// use rhai::{Engine, Scope, OptimizationLevel};
///
/// let mut engine = Engine::new();
///
/// // Set optimization level to 'Full' so the Engine can fold constants
/// // into function calls and operators.
/// engine.set_optimization_level(OptimizationLevel::Full);
///
/// // Create initialized scope
/// let mut scope = Scope::new();
/// scope.push_constant("x", 42_i64); // 'x' is a constant
///
/// // Compile a script made up of script segments to an AST and store it for later evaluation.
/// // Notice that `Full` optimization is on, so constants are folded
/// // into function calls and operators.
/// let ast = engine.compile_scripts_with_scope(&mut scope, &[
/// "if x > 40", // all 'x' are replaced with 42
/// "{ x } el",
/// "se { 0 }" // segments do not need to be valid scripts!
/// ])?;
///
/// // Normally this would have failed because no scope is passed into the 'eval_ast'
/// // call and so the variable 'x' does not exist. Here, it passes because the script
/// // has been optimized and all references to 'x' are already gone.
/// assert_eq!(engine.eval_ast::<i64>(&ast)?, 42);
/// # }
/// # Ok(())
/// # }
/// ```
pub fn compile_scripts_with_scope(
&self,
scope: &Scope,
scripts: &[&str],
) -> Result<AST, Box<ParseError>> {
self.compile_with_scope_and_optimization_level(scope, scripts, self.optimization_level)
}
/// Join a list of strings and compile into an `AST` using own scope at a specific optimization level.
pub(crate) fn compile_with_scope_and_optimization_level( pub(crate) fn compile_with_scope_and_optimization_level(
&self, &self,
scope: &Scope, scope: &Scope,
script: &str, scripts: &[&str],
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> Result<AST, Box<ParseError>> { ) -> Result<AST, Box<ParseError>> {
let scripts = [script]; let stream = lex(scripts);
let stream = lex(&scripts);
parse(&mut stream.peekable(), self, scope, optimization_level) parse(&mut stream.peekable(), self, scope, optimization_level)
} }
@ -487,6 +498,7 @@ impl Engine {
} }
/// Compile a script file into an `AST` using own scope, which can be used later for evaluation. /// Compile a script file into an `AST` using own scope, which can be used later for evaluation.
///
/// The scope is useful for passing constants into the script for optimization /// The scope is useful for passing constants into the script for optimization
/// when using `OptimizationLevel::Full`. /// when using `OptimizationLevel::Full`.
/// ///
@ -738,8 +750,11 @@ impl Engine {
script: &str, script: &str,
) -> Result<T, Box<EvalAltResult>> { ) -> Result<T, Box<EvalAltResult>> {
// Since the AST will be thrown away afterwards, don't bother to optimize it // Since the AST will be thrown away afterwards, don't bother to optimize it
let ast = let ast = self.compile_with_scope_and_optimization_level(
self.compile_with_scope_and_optimization_level(scope, script, OptimizationLevel::None)?; scope,
&[script],
OptimizationLevel::None,
)?;
self.eval_ast_with_scope(scope, &ast) self.eval_ast_with_scope(scope, &ast)
} }
@ -856,7 +871,7 @@ impl Engine {
return result.try_cast::<T>().ok_or_else(|| { return result.try_cast::<T>().ok_or_else(|| {
Box::new(EvalAltResult::ErrorMismatchOutputType( Box::new(EvalAltResult::ErrorMismatchOutputType(
return_type.to_string(), return_type.into(),
Position::none(), Position::none(),
)) ))
}); });
@ -867,12 +882,12 @@ impl Engine {
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
let mut state = State::new(); let mut state = State::new(ast.fn_lib());
ast.statements() ast.statements()
.iter() .iter()
.try_fold(().into(), |_, stmt| { .try_fold(().into(), |_, stmt| {
self.eval_stmt(scope, &mut state, ast.fn_lib(), stmt, 0) self.eval_stmt(scope, &mut state, stmt, 0)
}) })
.or_else(|err| match *err { .or_else(|err| match *err {
EvalAltResult::Return(out, _) => Ok(out), EvalAltResult::Return(out, _) => Ok(out),
@ -932,12 +947,12 @@ impl Engine {
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
let mut state = State::new(); let mut state = State::new(ast.fn_lib());
ast.statements() ast.statements()
.iter() .iter()
.try_fold(().into(), |_, stmt| { .try_fold(().into(), |_, stmt| {
self.eval_stmt(scope, &mut state, ast.fn_lib(), stmt, 0) self.eval_stmt(scope, &mut state, stmt, 0)
}) })
.map_or_else( .map_or_else(
|err| match *err { |err| match *err {
@ -992,15 +1007,18 @@ impl Engine {
args: A, args: A,
) -> Result<T, Box<EvalAltResult>> { ) -> Result<T, Box<EvalAltResult>> {
let mut arg_values = args.into_vec(); let mut arg_values = args.into_vec();
let mut args: Vec<_> = arg_values.iter_mut().collect(); let mut args: StaticVec<_> = arg_values.iter_mut().collect();
let fn_lib = ast.fn_lib(); let fn_lib = ast.fn_lib();
let pos = Position::none(); let pos = Position::none();
let fn_def = fn_lib let fn_def = fn_lib
.get_function(name, args.len()) .get_function_by_signature(name, args.len(), true)
.ok_or_else(|| Box::new(EvalAltResult::ErrorFunctionNotFound(name.to_string(), pos)))?; .ok_or_else(|| Box::new(EvalAltResult::ErrorFunctionNotFound(name.into(), pos)))?;
let result = self.call_fn_from_lib(Some(scope), fn_lib, fn_def, &mut args, pos, 0)?; let state = State::new(fn_lib);
let result =
self.call_script_fn(Some(scope), &state, name, fn_def, args.as_mut(), pos, 0)?;
let return_type = self.map_type_name(result.type_name()); let return_type = self.map_type_name(result.type_name());

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@ use crate::token::Position;
use crate::stdlib::{boxed::Box, char, error::Error, fmt, string::String}; use crate::stdlib::{boxed::Box, char, error::Error, fmt, string::String};
/// Error when tokenizing the script text. /// Error when tokenizing the script text.
#[derive(Debug, Eq, PartialEq, Hash, Clone)] #[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub enum LexError { pub enum LexError {
/// An unexpected character is encountered when tokenizing the script text. /// An unexpected character is encountered when tokenizing the script text.
UnexpectedChar(char), UnexpectedChar(char),
@ -44,7 +44,7 @@ impl fmt::Display for LexError {
/// Some errors never appear when certain features are turned on. /// Some errors never appear when certain features are turned on.
/// They still exist so that the application can turn features on and off without going through /// They still exist so that the application can turn features on and off without going through
/// massive code changes to remove/add back enum variants in match statements. /// massive code changes to remove/add back enum variants in match statements.
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub enum ParseErrorType { pub enum ParseErrorType {
/// Error in the script text. Wrapped value is the error message. /// Error in the script text. Wrapped value is the error message.
BadInput(String), BadInput(String),
@ -98,8 +98,16 @@ pub enum ParseErrorType {
/// ///
/// Never appears under the `no_function` feature. /// Never appears under the `no_function` feature.
FnMissingBody(String), FnMissingBody(String),
/// Assignment to an inappropriate LHS (left-hand-side) expression. /// An export statement has duplicated names.
AssignmentToInvalidLHS, ///
/// Never appears under the `no_module` feature.
DuplicatedExport(String),
/// Export statement not at global level.
///
/// Never appears under the `no_module` feature.
WrongExport,
/// Assignment to a copy of a value.
AssignmentToCopy,
/// Assignment to an a constant variable. /// Assignment to an a constant variable.
AssignmentToConstant(String), AssignmentToConstant(String),
/// Break statement not inside a loop. /// Break statement not inside a loop.
@ -114,7 +122,7 @@ impl ParseErrorType {
} }
/// Error when parsing a script. /// Error when parsing a script.
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub struct ParseError(pub(crate) ParseErrorType, pub(crate) Position); pub struct ParseError(pub(crate) ParseErrorType, pub(crate) Position);
impl ParseError { impl ParseError {
@ -147,8 +155,10 @@ impl ParseError {
ParseErrorType::FnDuplicatedParam(_,_) => "Duplicated parameters in function declaration", ParseErrorType::FnDuplicatedParam(_,_) => "Duplicated parameters in function declaration",
ParseErrorType::FnMissingBody(_) => "Expecting body statement block for function declaration", ParseErrorType::FnMissingBody(_) => "Expecting body statement block for function declaration",
ParseErrorType::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another function", ParseErrorType::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another function",
ParseErrorType::AssignmentToInvalidLHS => "Cannot assign to this expression", ParseErrorType::DuplicatedExport(_) => "Duplicated variable/function in export statement",
ParseErrorType::AssignmentToConstant(_) => "Cannot assign to a constant variable.", ParseErrorType::WrongExport => "Export statement can only appear at global level",
ParseErrorType::AssignmentToCopy => "Only a copy of the value is change with this assignment",
ParseErrorType::AssignmentToConstant(_) => "Cannot assign to a constant value.",
ParseErrorType::LoopBreak => "Break statement should only be used inside a loop" ParseErrorType::LoopBreak => "Break statement should only be used inside a loop"
} }
} }
@ -193,6 +203,12 @@ impl fmt::Display for ParseError {
write!(f, "Duplicated parameter '{}' for function '{}'", arg, s)? write!(f, "Duplicated parameter '{}' for function '{}'", arg, s)?
} }
ParseErrorType::DuplicatedExport(s) => write!(
f,
"Duplicated variable/function '{}' in export statement",
s
)?,
ParseErrorType::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s)?, ParseErrorType::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s)?,
ParseErrorType::AssignmentToConstant(s) if s.is_empty() => { ParseErrorType::AssignmentToConstant(s) if s.is_empty() => {

View File

@ -3,14 +3,14 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use crate::any::{Dynamic, Variant}; use crate::any::{Dynamic, Variant};
use crate::stdlib::vec::Vec; use crate::utils::StaticVec;
/// Trait that represents arguments to a function call. /// Trait that represents arguments to a function call.
/// Any data type that can be converted into a `Vec<Dynamic>` can be used /// Any data type that can be converted into a `Vec<Dynamic>` can be used
/// as arguments to a function call. /// as arguments to a function call.
pub trait FuncArgs { pub trait FuncArgs {
/// Convert to a `Vec<Dynamic>` of the function call arguments. /// Convert to a `Vec<Dynamic>` of the function call arguments.
fn into_vec(self) -> Vec<Dynamic>; fn into_vec(self) -> StaticVec<Dynamic>;
} }
/// Macro to implement `FuncArgs` for tuples of standard types (each can be /// Macro to implement `FuncArgs` for tuples of standard types (each can be
@ -19,11 +19,11 @@ macro_rules! impl_args {
($($p:ident),*) => { ($($p:ident),*) => {
impl<$($p: Variant + Clone),*> FuncArgs for ($($p,)*) impl<$($p: Variant + Clone),*> FuncArgs for ($($p,)*)
{ {
fn into_vec(self) -> Vec<Dynamic> { fn into_vec(self) -> StaticVec<Dynamic> {
let ($($p,)*) = self; let ($($p,)*) = self;
#[allow(unused_mut)] #[allow(unused_mut)]
let mut v = Vec::new(); let mut v = StaticVec::new();
$(v.push($p.into_dynamic());)* $(v.push($p.into_dynamic());)*
v v

View File

@ -92,15 +92,14 @@ macro_rules! def_anonymous_fn {
{ {
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
type Output = Box<dyn Fn($($par),*) -> Result<RET, Box<EvalAltResult>> + Send + Sync>; type Output = Box<dyn Fn($($par),*) -> Result<RET, Box<EvalAltResult>> + Send + Sync>;
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
type Output = Box<dyn Fn($($par),*) -> Result<RET, Box<EvalAltResult>>>; type Output = Box<dyn Fn($($par),*) -> Result<RET, Box<EvalAltResult>>>;
fn create_from_ast(self, ast: AST, entry_point: &str) -> Self::Output { fn create_from_ast(self, ast: AST, entry_point: &str) -> Self::Output {
let name = entry_point.to_string(); let fn_name = entry_point.to_string();
Box::new(move |$($par: $par),*| { Box::new(move |$($par: $par),*| {
self.call_fn(&mut Scope::new(), &ast, &name, ($($par,)*)) self.call_fn(&mut Scope::new(), &ast, &fn_name, ($($par,)*))
}) })
} }

134
src/fn_native.rs Normal file
View File

@ -0,0 +1,134 @@
use crate::any::Dynamic;
use crate::result::EvalAltResult;
use crate::stdlib::{boxed::Box, rc::Rc, sync::Arc};
pub type FnCallArgs<'a> = [&'a mut Dynamic];
#[cfg(feature = "sync")]
pub type FnAny = dyn Fn(&mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync;
#[cfg(not(feature = "sync"))]
pub type FnAny = dyn Fn(&mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>>;
#[cfg(feature = "sync")]
pub type IteratorFn = dyn Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + Send + Sync;
#[cfg(not(feature = "sync"))]
pub type IteratorFn = dyn Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
#[cfg(feature = "sync")]
pub type PrintCallback = dyn Fn(&str) + Send + Sync + 'static;
#[cfg(not(feature = "sync"))]
pub type PrintCallback = dyn Fn(&str) + 'static;
// Define callback function types
#[cfg(feature = "sync")]
pub trait ObjectGetCallback<T, U>: Fn(&mut T) -> U + Send + Sync + 'static {}
#[cfg(feature = "sync")]
impl<F: Fn(&mut T) -> U + Send + Sync + 'static, T, U> ObjectGetCallback<T, U> for F {}
#[cfg(not(feature = "sync"))]
pub trait ObjectGetCallback<T, U>: Fn(&mut T) -> U + 'static {}
#[cfg(not(feature = "sync"))]
impl<F: Fn(&mut T) -> U + 'static, T, U> ObjectGetCallback<T, U> for F {}
#[cfg(feature = "sync")]
pub trait ObjectSetCallback<T, U>: Fn(&mut T, U) + Send + Sync + 'static {}
#[cfg(feature = "sync")]
impl<F: Fn(&mut T, U) + Send + Sync + 'static, T, U> ObjectSetCallback<T, U> for F {}
#[cfg(not(feature = "sync"))]
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
{
}
#[cfg(feature = "sync")]
impl<F: Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + Send + Sync + 'static> IteratorCallback
for F
{
}
#[cfg(not(feature = "sync"))]
pub trait IteratorCallback: Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + 'static {}
#[cfg(not(feature = "sync"))]
impl<F: Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + 'static> IteratorCallback for F {}
/// A type representing the type of ABI of a native Rust function.
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
pub enum NativeFunctionABI {
/// A pure function where all arguments are passed by value.
Pure,
/// An object method where the first argument is the object passed by mutable reference.
/// All other arguments are passed by value.
Method,
}
/// A trait implemented by all native Rust functions that are callable by Rhai.
#[cfg(not(feature = "sync"))]
pub trait NativeCallable {
/// Get the ABI type of a native Rust function.
fn abi(&self) -> NativeFunctionABI;
/// Call a native Rust function.
fn call(&self, args: &mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>>;
}
/// A trait implemented by all native Rust functions that are callable by Rhai.
#[cfg(feature = "sync")]
pub trait NativeCallable: Send + Sync {
/// Get the ABI type of a native Rust function.
fn abi(&self) -> NativeFunctionABI;
/// Call a native Rust function.
fn call(&self, args: &mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>>;
}
/// A type encapsulating a native Rust function callable by Rhai.
pub struct NativeFunction(Box<FnAny>, NativeFunctionABI);
impl NativeCallable for NativeFunction {
fn abi(&self) -> NativeFunctionABI {
self.1
}
fn call(&self, args: &mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>> {
(self.0)(args)
}
}
impl From<(Box<FnAny>, NativeFunctionABI)> for NativeFunction {
fn from(func: (Box<FnAny>, NativeFunctionABI)) -> Self {
Self::new(func.0, func.1)
}
}
impl NativeFunction {
/// Create a new `NativeFunction`.
pub fn new(func: Box<FnAny>, abi: NativeFunctionABI) -> Self {
Self(func, abi)
}
}
/// An external native Rust function.
#[cfg(not(feature = "sync"))]
pub type SharedNativeFunction = Rc<Box<dyn NativeCallable>>;
/// An external native Rust function.
#[cfg(feature = "sync")]
pub type SharedNativeFunction = Arc<Box<dyn NativeCallable>>;
/// A type iterator function.
#[cfg(not(feature = "sync"))]
pub type SharedIteratorFunction = Rc<Box<IteratorFn>>;
/// An external native Rust function.
#[cfg(feature = "sync")]
pub type SharedIteratorFunction = Arc<Box<IteratorFn>>;

View File

@ -3,10 +3,10 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use crate::any::{Dynamic, Variant}; use crate::any::{Dynamic, Variant};
use crate::engine::{Engine, FnCallArgs}; use crate::engine::Engine;
use crate::fn_native::{FnCallArgs, NativeFunctionABI::*};
use crate::parser::FnAccess;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::token::Position;
use crate::utils::calc_fn_spec;
use crate::stdlib::{any::TypeId, boxed::Box, mem, string::ToString}; use crate::stdlib::{any::TypeId, boxed::Box, mem, string::ToString};
@ -117,42 +117,30 @@ pub struct Mut<T>(T);
//pub struct Ref<T>(T); //pub struct Ref<T>(T);
/// Dereference into &mut. /// Dereference into &mut.
#[inline] #[inline(always)]
pub fn by_ref<T: Clone + 'static>(data: &mut Dynamic) -> &mut T { pub fn by_ref<T: Variant + Clone>(data: &mut Dynamic) -> &mut T {
// Directly cast the &mut Dynamic into &mut T to access the underlying data. // Directly cast the &mut Dynamic into &mut T to access the underlying data.
data.downcast_mut::<T>().unwrap() data.downcast_mut::<T>().unwrap()
} }
/// Dereference into value. /// Dereference into value.
#[inline] #[inline(always)]
pub fn by_value<T: Clone + 'static>(data: &mut Dynamic) -> T { pub fn by_value<T: Variant + Clone>(data: &mut Dynamic) -> T {
// We consume the argument and then replace it with () - the argument is not supposed to be used again. // We consume the argument and then replace it with () - the argument is not supposed to be used again.
// This way, we avoid having to clone the argument again, because it is already a clone when passed here. // This way, we avoid having to clone the argument again, because it is already a clone when passed here.
mem::take(data).cast::<T>() mem::take(data).cast::<T>()
} }
/// This macro counts the number of arguments via recursion.
macro_rules! count_args {
() => { 0_usize };
( $head:ident $($tail:ident)* ) => { 1_usize + count_args!($($tail)*) };
}
/// This macro creates a closure wrapping a registered function. /// This macro creates a closure wrapping a registered function.
macro_rules! make_func { macro_rules! make_func {
($fn_name:ident : $fn:ident : $map:expr ; $($par:ident => $convert:expr),*) => { ($fn:ident : $map:expr ; $($par:ident => $convert:expr),*) => {
// ^ function name // ^ function pointer
// ^ function pointer // ^ result mapping function
// ^ result mapping function // ^ function parameter generic type name (A, B, C etc.)
// ^ function parameter generic type name (A, B, C etc.) // ^ dereferencing function
// ^ dereferencing function
move |args: &mut FnCallArgs, pos: Position| { Box::new(move |args: &mut FnCallArgs| {
// Check for length at the beginning to avoid per-element bound checks. // The arguments are assumed to be of the correct number and types!
const NUM_ARGS: usize = count_args!($($par)*);
if args.len() != NUM_ARGS {
return Err(Box::new(EvalAltResult::ErrorFunctionArgsMismatch($fn_name.clone(), NUM_ARGS, args.len(), pos)));
}
#[allow(unused_variables, unused_mut)] #[allow(unused_variables, unused_mut)]
let mut drain = args.iter_mut(); let mut drain = args.iter_mut();
@ -166,45 +154,41 @@ macro_rules! make_func {
let r = $fn($($par),*); let r = $fn($($par),*);
// Map the result // Map the result
$map(r, pos) $map(r)
}; })
}; };
} }
/// To Dynamic mapping function. /// To Dynamic mapping function.
#[inline] #[inline(always)]
pub fn map_dynamic<T: Variant + Clone>( pub fn map_dynamic<T: Variant + Clone>(data: T) -> Result<Dynamic, Box<EvalAltResult>> {
data: T,
_pos: Position,
) -> Result<Dynamic, Box<EvalAltResult>> {
Ok(data.into_dynamic()) Ok(data.into_dynamic())
} }
/// To Dynamic mapping function. /// To Dynamic mapping function.
#[inline] #[inline(always)]
pub fn map_identity(data: Dynamic, _pos: Position) -> Result<Dynamic, Box<EvalAltResult>> { pub fn map_identity(data: Dynamic) -> Result<Dynamic, Box<EvalAltResult>> {
Ok(data) Ok(data)
} }
/// To `Result<Dynamic, Box<EvalAltResult>>` mapping function. /// To `Result<Dynamic, Box<EvalAltResult>>` mapping function.
#[inline] #[inline(always)]
pub fn map_result<T: Variant + Clone>( pub fn map_result<T: Variant + Clone>(
data: Result<T, Box<EvalAltResult>>, data: Result<T, Box<EvalAltResult>>,
pos: Position,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
data.map(|v| v.into_dynamic()) data.map(|v| v.into_dynamic())
.map_err(|err| EvalAltResult::set_position(err, pos))
} }
macro_rules! def_register { macro_rules! def_register {
() => { () => {
def_register!(imp); def_register!(imp Pure;);
}; };
(imp $($par:ident => $mark:ty => $param:ty => $clone:expr),*) => { (imp $abi:expr ; $($par:ident => $mark:ty => $param:ty => $clone:expr),*) => {
// ^ function parameter generic type name (A, B, C etc.) // ^ function ABI type
// ^ function parameter marker type (T, Ref<T> or Mut<T>) // ^ function parameter generic type name (A, B, C etc.)
// ^ function parameter actual type (T, &T or &mut T) // ^ function parameter marker type (T, Ref<T> or Mut<T>)
// ^ dereferencing function // ^ function parameter actual type (T, &T or &mut T)
// ^ dereferencing function
impl< impl<
$($par: Variant + Clone,)* $($par: Variant + Clone,)*
@ -218,10 +202,10 @@ macro_rules! def_register {
> RegisterFn<FN, ($($mark,)*), RET> for Engine > RegisterFn<FN, ($($mark,)*), RET> for Engine
{ {
fn register_fn(&mut self, name: &str, f: FN) { fn register_fn(&mut self, name: &str, f: FN) {
let fn_name = name.to_string(); self.global_module.set_fn(name.to_string(), $abi, FnAccess::Public,
let func = make_func!(fn_name : f : map_dynamic ; $($par => $clone),*); &[$(TypeId::of::<$par>()),*],
let hash = calc_fn_spec(name, [$(TypeId::of::<$par>()),*].iter().cloned()); make_func!(f : map_dynamic ; $($par => $clone),*)
self.functions.insert(hash, Box::new(func)); );
} }
} }
@ -236,10 +220,10 @@ macro_rules! def_register {
> RegisterDynamicFn<FN, ($($mark,)*)> for Engine > RegisterDynamicFn<FN, ($($mark,)*)> for Engine
{ {
fn register_dynamic_fn(&mut self, name: &str, f: FN) { fn register_dynamic_fn(&mut self, name: &str, f: FN) {
let fn_name = name.to_string(); self.global_module.set_fn(name.to_string(), $abi, FnAccess::Public,
let func = make_func!(fn_name : f : map_identity ; $($par => $clone),*); &[$(TypeId::of::<$par>()),*],
let hash = calc_fn_spec(name, [$(TypeId::of::<$par>()),*].iter().cloned()); make_func!(f : map_identity ; $($par => $clone),*)
self.functions.insert(hash, Box::new(func)); );
} }
} }
@ -255,20 +239,20 @@ macro_rules! def_register {
> RegisterResultFn<FN, ($($mark,)*), RET> for Engine > RegisterResultFn<FN, ($($mark,)*), RET> for Engine
{ {
fn register_result_fn(&mut self, name: &str, f: FN) { fn register_result_fn(&mut self, name: &str, f: FN) {
let fn_name = name.to_string(); self.global_module.set_fn(name.to_string(), $abi, FnAccess::Public,
let func = make_func!(fn_name : f : map_result ; $($par => $clone),*); &[$(TypeId::of::<$par>()),*],
let hash = calc_fn_spec(name, [$(TypeId::of::<$par>()),*].iter().cloned()); make_func!(f : map_result ; $($par => $clone),*)
self.functions.insert(hash, Box::new(func)); );
} }
} }
//def_register!(imp_pop $($par => $mark => $param),*); //def_register!(imp_pop $($par => $mark => $param),*);
}; };
($p0:ident $(, $p:ident)*) => { ($p0:ident $(, $p:ident)*) => {
def_register!(imp $p0 => $p0 => $p0 => by_value $(, $p => $p => $p => by_value)*); def_register!(imp Pure ; $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)*); def_register!(imp Method ; $p0 => Mut<$p0> => &mut $p0 => by_ref $(, $p => $p => $p => by_value)*);
// handle the first parameter ^ first parameter passed through // handle the first parameter ^ first parameter passed through
// ^ others passed by value (by_value) // ^ others passed by value (by_value)
// Currently does not support first argument which is a reference, as there will be // 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 // conflicting implementations since &T: Any and T: Any cannot be distinguished

View File

@ -75,6 +75,7 @@ mod engine;
mod error; mod error;
mod fn_call; mod fn_call;
mod fn_func; mod fn_func;
mod fn_native;
mod fn_register; mod fn_register;
mod module; mod module;
mod optimize; mod optimize;
@ -84,13 +85,16 @@ mod result;
mod scope; mod scope;
mod stdlib; mod stdlib;
mod token; mod token;
mod r#unsafe;
mod utils; mod utils;
pub use any::Dynamic; pub use any::Dynamic;
pub use engine::Engine; pub use engine::Engine;
pub use error::{ParseError, ParseErrorType}; pub use error::{ParseError, ParseErrorType};
pub use fn_call::FuncArgs; pub use fn_call::FuncArgs;
pub use fn_native::NativeCallable;
pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn}; pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
pub use module::Module;
pub use parser::{AST, INT}; pub use parser::{AST, INT};
pub use result::EvalAltResult; pub use result::EvalAltResult;
pub use scope::Scope; pub use scope::Scope;
@ -110,7 +114,7 @@ pub use engine::Map;
pub use parser::FLOAT; pub use parser::FLOAT;
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub use module::{Module, ModuleResolver}; pub use module::ModuleResolver;
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub mod module_resolvers { pub mod module_resolvers {

View File

@ -1,61 +1,69 @@
//! Module defining external-loaded modules for Rhai. //! Module defining external-loaded modules for Rhai.
#![cfg(not(feature = "no_module"))]
use crate::any::{Dynamic, Variant}; use crate::any::{Dynamic, Variant};
use crate::calc_fn_hash; use crate::calc_fn_hash;
use crate::engine::{Engine, FnAny, FnCallArgs, FunctionsLib}; use crate::engine::{Engine, FunctionsLib};
use crate::parser::{FnDef, AST}; use crate::fn_native::{
FnAny, FnCallArgs, IteratorFn, NativeCallable, NativeFunction, NativeFunctionABI,
NativeFunctionABI::*, SharedIteratorFunction, SharedNativeFunction,
};
use crate::parser::{FnAccess, FnDef, SharedFnDef, AST};
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
use crate::token::Position; use crate::token::{Position, Token};
use crate::token::Token; use crate::utils::{StaticVec, EMPTY_TYPE_ID};
use crate::utils::StaticVec;
use crate::stdlib::{ use crate::stdlib::{
any::TypeId, any::TypeId,
boxed::Box, boxed::Box,
collections::HashMap, collections::HashMap,
fmt, mem, fmt,
iter::{empty, repeat},
mem,
num::NonZeroUsize,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
rc::Rc, rc::Rc,
string::{String, ToString}, string::{String, ToString},
sync::Arc, sync::Arc,
vec,
vec::Vec,
}; };
/// A trait that encapsulates a module resolution service. /// Default function access mode.
pub trait ModuleResolver { const DEF_ACCESS: FnAccess = FnAccess::Public;
/// 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. /// Return type of module-level Rust function.
type FuncReturn<T> = Result<T, Box<EvalAltResult>>; pub type FuncReturn<T> = Result<T, Box<EvalAltResult>>;
/// An imported module, which may contain variables, sub-modules, /// An imported module, which may contain variables, sub-modules,
/// external Rust functions, and script-defined functions. /// external Rust functions, and script-defined functions.
/// ///
/// Not available under the `no_module` feature. /// Not available under the `no_module` feature.
#[derive(Default, Clone)] #[derive(Clone, Default)]
pub struct Module { pub struct Module {
/// Sub-modules. /// Sub-modules.
modules: HashMap<String, Module>, modules: HashMap<String, Module>,
/// Module variables, including sub-modules.
/// Module variables.
variables: HashMap<String, Dynamic>, variables: HashMap<String, Dynamic>,
/// Flattened collection of all module variables, including those in sub-modules.
all_variables: HashMap<u64, Dynamic>,
/// External Rust functions. /// External Rust functions.
#[cfg(not(feature = "sync"))] functions: HashMap<u64, (String, FnAccess, Vec<TypeId>, SharedNativeFunction)>,
functions: HashMap<u64, Rc<Box<FnAny>>>,
/// External Rust functions. /// Flattened collection of all external Rust functions, including those in sub-modules.
#[cfg(feature = "sync")] all_functions: HashMap<u64, SharedNativeFunction>,
functions: HashMap<u64, Arc<Box<FnAny>>>,
/// Script-defined functions. /// Script-defined functions.
fn_lib: FunctionsLib, fn_lib: FunctionsLib,
/// Flattened collection of all script-defined functions, including those in sub-modules.
all_fn_lib: FunctionsLib,
/// Iterator functions, keyed by the type producing the iterator.
type_iterators: HashMap<TypeId, SharedIteratorFunction>,
} }
impl fmt::Debug for Module { impl fmt::Debug for Module {
@ -86,6 +94,24 @@ impl Module {
Default::default() Default::default()
} }
/// Create a new module with a specified capacity for native Rust functions.
///
/// # 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_with_capacity(capacity: usize) -> Self {
Self {
functions: HashMap::with_capacity(capacity),
..Default::default()
}
}
/// Does a variable exist in the module? /// Does a variable exist in the module?
/// ///
/// # Examples /// # Examples
@ -113,7 +139,7 @@ impl Module {
/// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42); /// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42);
/// ``` /// ```
pub fn get_var_value<T: Variant + Clone>(&self, name: &str) -> Option<T> { pub fn get_var_value<T: Variant + Clone>(&self, name: &str) -> Option<T> {
self.get_var(name).and_then(|v| v.try_cast::<T>()) self.get_var(name).and_then(Dynamic::try_cast::<T>)
} }
/// Get a module variable as a `Dynamic`. /// Get a module variable as a `Dynamic`.
@ -131,11 +157,6 @@ impl Module {
self.variables.get(name).cloned() 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. /// Set a variable into the module.
/// ///
/// If there is an existing variable of the same name, it is replaced. /// If there is an existing variable of the same name, it is replaced.
@ -149,21 +170,22 @@ impl Module {
/// module.set_var("answer", 42_i64); /// module.set_var("answer", 42_i64);
/// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42); /// 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) { pub fn set_var<K: Into<String>, T: Variant + Clone>(&mut self, name: K, value: T) {
self.variables.insert(name.into(), value.into()); self.variables.insert(name.into(), Dynamic::from(value));
} }
/// Get a mutable reference to a modules-qualified variable. /// Get a mutable reference to a modules-qualified variable.
///
/// The `u64` hash is calculated by the function `crate::calc_fn_hash`.
pub(crate) fn get_qualified_var_mut( pub(crate) fn get_qualified_var_mut(
&mut self, &mut self,
name: &str, name: &str,
modules: &StaticVec<(String, Position)>, hash_var: u64,
pos: Position, pos: Position,
) -> Result<&mut Dynamic, Box<EvalAltResult>> { ) -> Result<&mut Dynamic, Box<EvalAltResult>> {
Ok(self self.all_variables
.get_qualified_module_mut(modules)? .get_mut(&hash_var)
.get_var_mut(name) .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.to_string(), pos)))
.ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))?)
} }
/// Does a sub-module exist in the module? /// Does a sub-module exist in the module?
@ -232,26 +254,6 @@ impl Module {
self.modules.insert(name.into(), sub_module.into()); 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? /// Does the particular Rust function exist in the module?
/// ///
/// The `u64` hash is calculated by the function `crate::calc_fn_hash`. /// The `u64` hash is calculated by the function `crate::calc_fn_hash`.
@ -266,22 +268,34 @@ impl Module {
/// let hash = module.set_fn_0("calc", || Ok(42_i64)); /// let hash = module.set_fn_0("calc", || Ok(42_i64));
/// assert!(module.contains_fn(hash)); /// assert!(module.contains_fn(hash));
/// ``` /// ```
pub fn contains_fn(&self, hash: u64) -> bool { pub fn contains_fn(&self, hash_fn: u64) -> bool {
self.functions.contains_key(&hash) self.functions.contains_key(&hash_fn)
} }
/// Set a Rust function into the module, returning a hash key. /// 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. /// 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 { pub fn set_fn(
let hash = calc_fn_hash(fn_name, params.iter().cloned()); &mut self,
name: String,
abi: NativeFunctionABI,
access: FnAccess,
params: &[TypeId],
func: Box<FnAny>,
) -> u64 {
let hash_fn = calc_fn_hash(empty(), &name, params.iter().cloned());
let f = Box::new(NativeFunction::from((func, abi))) as Box<dyn NativeCallable>;
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
self.functions.insert(hash, Rc::new(func)); let func = Rc::new(f);
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
self.functions.insert(hash, Arc::new(func)); let func = Arc::new(f);
hash self.functions
.insert(hash_fn, (name, access, params.to_vec(), func));
hash_fn
} }
/// Set a Rust function taking no parameters into the module, returning a hash key. /// Set a Rust function taking no parameters into the module, returning a hash key.
@ -297,19 +311,15 @@ impl Module {
/// let hash = module.set_fn_0("calc", || Ok(42_i64)); /// let hash = module.set_fn_0("calc", || Ok(42_i64));
/// assert!(module.get_fn(hash).is_some()); /// assert!(module.get_fn(hash).is_some());
/// ``` /// ```
pub fn set_fn_0<T: Into<Dynamic>>( pub fn set_fn_0<K: Into<String>, T: Variant + Clone>(
&mut self, &mut self,
fn_name: &str, name: K,
#[cfg(not(feature = "sync"))] func: impl Fn() -> FuncReturn<T> + 'static, #[cfg(not(feature = "sync"))] func: impl Fn() -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn() -> FuncReturn<T> + Send + Sync + 'static, #[cfg(feature = "sync")] func: impl Fn() -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 { ) -> u64 {
let f = move |_: &mut FnCallArgs, pos| { let f = move |_: &mut FnCallArgs| func().map(Dynamic::from);
func() let arg_types = [];
.map(|v| v.into()) self.set_fn(name.into(), Pure, DEF_ACCESS, &arg_types, Box::new(f))
.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. /// Set a Rust function taking one parameter into the module, returning a hash key.
@ -325,19 +335,16 @@ impl Module {
/// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1)); /// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1));
/// assert!(module.get_fn(hash).is_some()); /// assert!(module.get_fn(hash).is_some());
/// ``` /// ```
pub fn set_fn_1<A: Variant + Clone, T: Into<Dynamic>>( pub fn set_fn_1<K: Into<String>, A: Variant + Clone, T: Variant + Clone>(
&mut self, &mut self,
fn_name: &str, name: K,
#[cfg(not(feature = "sync"))] func: impl Fn(A) -> FuncReturn<T> + 'static, #[cfg(not(feature = "sync"))] func: impl Fn(A) -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn(A) -> FuncReturn<T> + Send + Sync + 'static, #[cfg(feature = "sync")] func: impl Fn(A) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 { ) -> u64 {
let f = move |args: &mut FnCallArgs, pos| { let f =
func(mem::take(args[0]).cast::<A>()) move |args: &mut FnCallArgs| func(mem::take(args[0]).cast::<A>()).map(Dynamic::from);
.map(|v| v.into()) let arg_types = [TypeId::of::<A>()];
.map_err(|err| EvalAltResult::set_position(err, pos)) self.set_fn(name.into(), Pure, DEF_ACCESS, &arg_types, Box::new(f))
};
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. /// Set a Rust function taking one mutable parameter into the module, returning a hash key.
@ -353,19 +360,17 @@ impl Module {
/// let hash = module.set_fn_1_mut("calc", |x: &mut i64| { *x += 1; Ok(*x) }); /// let hash = module.set_fn_1_mut("calc", |x: &mut i64| { *x += 1; Ok(*x) });
/// assert!(module.get_fn(hash).is_some()); /// assert!(module.get_fn(hash).is_some());
/// ``` /// ```
pub fn set_fn_1_mut<A: Variant + Clone, T: Into<Dynamic>>( pub fn set_fn_1_mut<K: Into<String>, A: Variant + Clone, T: Variant + Clone>(
&mut self, &mut self,
fn_name: &str, name: K,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A) -> FuncReturn<T> + 'static, #[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, #[cfg(feature = "sync")] func: impl Fn(&mut A) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 { ) -> u64 {
let f = move |args: &mut FnCallArgs, pos| { let f = move |args: &mut FnCallArgs| {
func(args[0].downcast_mut::<A>().unwrap()) func(args[0].downcast_mut::<A>().unwrap()).map(Dynamic::from)
.map(|v| v.into())
.map_err(|err| EvalAltResult::set_position(err, pos))
}; };
let arg_types = &[TypeId::of::<A>()]; let arg_types = [TypeId::of::<A>()];
self.set_fn(fn_name, arg_types, Box::new(f)) self.set_fn(name.into(), Method, DEF_ACCESS, &arg_types, Box::new(f))
} }
/// Set a Rust function taking two parameters into the module, returning a hash key. /// Set a Rust function taking two parameters into the module, returning a hash key.
@ -383,22 +388,20 @@ impl Module {
/// }); /// });
/// assert!(module.get_fn(hash).is_some()); /// assert!(module.get_fn(hash).is_some());
/// ``` /// ```
pub fn set_fn_2<A: Variant + Clone, B: Variant + Clone, T: Into<Dynamic>>( pub fn set_fn_2<K: Into<String>, A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
&mut self, &mut self,
fn_name: &str, name: K,
#[cfg(not(feature = "sync"))] func: impl Fn(A, B) -> FuncReturn<T> + 'static, #[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, #[cfg(feature = "sync")] func: impl Fn(A, B) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 { ) -> u64 {
let f = move |args: &mut FnCallArgs, pos| { let f = move |args: &mut FnCallArgs| {
let a = mem::take(args[0]).cast::<A>(); let a = mem::take(args[0]).cast::<A>();
let b = mem::take(args[1]).cast::<B>(); let b = mem::take(args[1]).cast::<B>();
func(a, b) func(a, b).map(Dynamic::from)
.map(|v| v.into())
.map_err(|err| EvalAltResult::set_position(err, pos))
}; };
let arg_types = &[TypeId::of::<A>(), TypeId::of::<B>()]; let arg_types = [TypeId::of::<A>(), TypeId::of::<B>()];
self.set_fn(fn_name, arg_types, Box::new(f)) self.set_fn(name.into(), Pure, DEF_ACCESS, &arg_types, Box::new(f))
} }
/// Set a Rust function taking two parameters (the first one mutable) into the module, /// Set a Rust function taking two parameters (the first one mutable) into the module,
@ -415,22 +418,25 @@ impl Module {
/// }); /// });
/// assert!(module.get_fn(hash).is_some()); /// assert!(module.get_fn(hash).is_some());
/// ``` /// ```
pub fn set_fn_2_mut<A: Variant + Clone, B: Variant + Clone, T: Into<Dynamic>>( pub fn set_fn_2_mut<
K: Into<String>,
A: Variant + Clone,
B: Variant + Clone,
T: Variant + Clone,
>(
&mut self, &mut self,
fn_name: &str, name: K,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> FuncReturn<T> + 'static, #[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, #[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 { ) -> u64 {
let f = move |args: &mut FnCallArgs, pos| { let f = move |args: &mut FnCallArgs| {
let b = mem::take(args[1]).cast::<B>(); let b = mem::take(args[1]).cast::<B>();
let a = args[0].downcast_mut::<A>().unwrap(); let a = args[0].downcast_mut::<A>().unwrap();
func(a, b) func(a, b).map(Dynamic::from)
.map(|v| v.into())
.map_err(|err| EvalAltResult::set_position(err, pos))
}; };
let arg_types = &[TypeId::of::<A>(), TypeId::of::<B>()]; let arg_types = [TypeId::of::<A>(), TypeId::of::<B>()];
self.set_fn(fn_name, arg_types, Box::new(f)) self.set_fn(name.into(), Method, DEF_ACCESS, &arg_types, Box::new(f))
} }
/// Set a Rust function taking three parameters into the module, returning a hash key. /// Set a Rust function taking three parameters into the module, returning a hash key.
@ -449,27 +455,26 @@ impl Module {
/// assert!(module.get_fn(hash).is_some()); /// assert!(module.get_fn(hash).is_some());
/// ``` /// ```
pub fn set_fn_3< pub fn set_fn_3<
K: Into<String>,
A: Variant + Clone, A: Variant + Clone,
B: Variant + Clone, B: Variant + Clone,
C: Variant + Clone, C: Variant + Clone,
T: Into<Dynamic>, T: Variant + Clone,
>( >(
&mut self, &mut self,
fn_name: &str, name: K,
#[cfg(not(feature = "sync"))] func: impl Fn(A, B, C) -> FuncReturn<T> + 'static, #[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, #[cfg(feature = "sync")] func: impl Fn(A, B, C) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 { ) -> u64 {
let f = move |args: &mut FnCallArgs, pos| { let f = move |args: &mut FnCallArgs| {
let a = mem::take(args[0]).cast::<A>(); let a = mem::take(args[0]).cast::<A>();
let b = mem::take(args[1]).cast::<B>(); let b = mem::take(args[1]).cast::<B>();
let c = mem::take(args[2]).cast::<C>(); let c = mem::take(args[2]).cast::<C>();
func(a, b, c) func(a, b, c).map(Dynamic::from)
.map(|v| v.into())
.map_err(|err| EvalAltResult::set_position(err, pos))
}; };
let arg_types = &[TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()]; let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn(fn_name, arg_types, Box::new(f)) self.set_fn(name.into(), Pure, DEF_ACCESS, &arg_types, Box::new(f))
} }
/// Set a Rust function taking three parameters (the first one mutable) into the module, /// Set a Rust function taking three parameters (the first one mutable) into the module,
@ -489,27 +494,26 @@ impl Module {
/// assert!(module.get_fn(hash).is_some()); /// assert!(module.get_fn(hash).is_some());
/// ``` /// ```
pub fn set_fn_3_mut< pub fn set_fn_3_mut<
K: Into<String>,
A: Variant + Clone, A: Variant + Clone,
B: Variant + Clone, B: Variant + Clone,
C: Variant + Clone, C: Variant + Clone,
T: Into<Dynamic>, T: Variant + Clone,
>( >(
&mut self, &mut self,
fn_name: &str, name: K,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B, C) -> FuncReturn<T> + 'static, #[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, #[cfg(feature = "sync")] func: impl Fn(&mut A, B, C) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 { ) -> u64 {
let f = move |args: &mut FnCallArgs, pos| { let f = move |args: &mut FnCallArgs| {
let b = mem::take(args[1]).cast::<B>(); let b = mem::take(args[1]).cast::<B>();
let c = mem::take(args[2]).cast::<C>(); let c = mem::take(args[2]).cast::<C>();
let a = args[0].downcast_mut::<A>().unwrap(); let a = args[0].downcast_mut::<A>().unwrap();
func(a, b, c) func(a, b, c).map(Dynamic::from)
.map(|v| v.into())
.map_err(|err| EvalAltResult::set_position(err, pos))
}; };
let arg_types = &[TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()]; let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn(fn_name, arg_types, Box::new(f)) self.set_fn(name.into(), Method, DEF_ACCESS, &arg_types, Box::new(f))
} }
/// Get a Rust function. /// Get a Rust function.
@ -526,8 +530,8 @@ impl Module {
/// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1)); /// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1));
/// assert!(module.get_fn(hash).is_some()); /// assert!(module.get_fn(hash).is_some());
/// ``` /// ```
pub fn get_fn(&self, hash: u64) -> Option<&Box<FnAny>> { pub fn get_fn(&self, hash_fn: u64) -> Option<&Box<dyn NativeCallable>> {
self.functions.get(&hash).map(|v| v.as_ref()) self.functions.get(&hash_fn).map(|(_, _, _, v)| v.as_ref())
} }
/// Get a modules-qualified function. /// Get a modules-qualified function.
@ -537,52 +541,24 @@ impl Module {
pub(crate) fn get_qualified_fn( pub(crate) fn get_qualified_fn(
&mut self, &mut self,
name: &str, name: &str,
hash: u64, hash_fn_native: u64,
modules: &StaticVec<(String, Position)>, ) -> Result<&Box<dyn NativeCallable>, Box<EvalAltResult>> {
pos: Position, self.all_functions
) -> Result<&Box<FnAny>, Box<EvalAltResult>> { .get(&hash_fn_native)
Ok(self .map(|f| f.as_ref())
.get_qualified_module_mut(modules)?
.get_fn(hash)
.ok_or_else(|| { .ok_or_else(|| {
let mut fn_name: String = Default::default(); Box::new(EvalAltResult::ErrorFunctionNotFound(
name.to_string(),
modules.iter().for_each(|(n, _)| { Position::none(),
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. /// Get a modules-qualified script-defined functions.
/// ///
/// # Examples /// The `u64` hash is calculated by the function `crate::calc_fn_hash`.
/// pub(crate) fn get_qualified_scripted_fn(&mut self, hash_fn_def: u64) -> Option<&FnDef> {
/// ``` self.all_fn_lib.get_function(hash_fn_def)
/// 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`. /// Create a new `Module` by evaluating an `AST`.
@ -591,20 +567,18 @@ impl Module {
/// ///
/// ``` /// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> { /// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// use rhai::{Engine, Module}; /// use rhai::{Engine, Module, Scope};
/// ///
/// let engine = Engine::new(); /// let engine = Engine::new();
/// let ast = engine.compile("let answer = 42;")?; /// let ast = engine.compile("let answer = 42; export answer;")?;
/// let module = Module::eval_ast_as_new(&ast, &engine)?; /// let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
/// assert!(module.contains_var("answer")); /// assert!(module.contains_var("answer"));
/// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42); /// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42);
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn eval_ast_as_new(ast: &AST, engine: &Engine) -> FuncReturn<Self> { #[cfg(not(feature = "no_module"))]
// Use new scope pub fn eval_ast_as_new(mut scope: Scope, ast: &AST, engine: &Engine) -> FuncReturn<Self> {
let mut scope = Scope::new();
// Run the script // Run the script
engine.eval_ast_with_scope_raw(&mut scope, &ast)?; engine.eval_ast_with_scope_raw(&mut scope, &ast)?;
@ -613,19 +587,21 @@ impl Module {
scope.into_iter().for_each( scope.into_iter().for_each(
|ScopeEntry { |ScopeEntry {
name, typ, value, .. typ, value, alias, ..
}| { }| {
match typ { match typ {
// Variables left in the scope become module variables // Variables with an alias left in the scope become module variables
ScopeEntryType::Normal | ScopeEntryType::Constant => { ScopeEntryType::Normal | ScopeEntryType::Constant if alias.is_some() => {
module.variables.insert(name.into_owned(), value); module.variables.insert(*alias.unwrap(), value);
} }
// Modules left in the scope become sub-modules // Modules left in the scope become sub-modules
ScopeEntryType::Module => { ScopeEntryType::Module if alias.is_some() => {
module module
.modules .modules
.insert(name.into_owned(), value.cast::<Module>()); .insert(*alias.unwrap(), value.cast::<Module>());
} }
// Variables and modules with no alias are private and not exported
_ => (),
} }
}, },
); );
@ -634,9 +610,195 @@ impl Module {
Ok(module) Ok(module)
} }
/// Scan through all the sub-modules in the `Module` build an index of all
/// variables and external Rust functions via hashing.
pub(crate) fn index_all_sub_modules(&mut self) {
// Collect a particular module.
fn index_module<'a>(
module: &'a mut Module,
qualifiers: &mut Vec<&'a str>,
variables: &mut Vec<(u64, Dynamic)>,
functions: &mut Vec<(u64, SharedNativeFunction)>,
fn_lib: &mut Vec<(u64, SharedFnDef)>,
) {
for (name, m) in module.modules.iter_mut() {
// Index all the sub-modules first.
qualifiers.push(name);
index_module(m, qualifiers, variables, functions, fn_lib);
qualifiers.pop();
}
// Index all variables
for (var_name, value) in module.variables.iter() {
// Qualifiers + variable name
let hash_var = calc_fn_hash(qualifiers.iter().map(|&v| v), var_name, empty());
variables.push((hash_var, value.clone()));
}
// Index all Rust functions
for (name, access, params, func) in module.functions.values() {
match access {
// Private functions are not exported
FnAccess::Private => continue,
FnAccess::Public => (),
}
// Rust functions are indexed in two steps:
// 1) Calculate a hash in a similar manner to script-defined functions,
// i.e. qualifiers + function name + dummy parameter types (one for each parameter).
let hash_fn_def = calc_fn_hash(
qualifiers.iter().map(|&v| v),
name,
repeat(EMPTY_TYPE_ID()).take(params.len()),
);
// 2) Calculate a second hash with no qualifiers, empty function name, and
// the actual list of parameter `TypeId`'.s
let hash_fn_args = calc_fn_hash(empty(), "", params.iter().cloned());
// 3) The final hash is the XOR of the two hashes.
let hash_fn_native = hash_fn_def ^ hash_fn_args;
functions.push((hash_fn_native, func.clone()));
}
// Index all script-defined functions
for fn_def in module.fn_lib.values() {
match fn_def.access {
// Private functions are not exported
FnAccess::Private => continue,
DEF_ACCESS => (),
}
// Qualifiers + function name + placeholders (one for each parameter)
let hash_fn_def = calc_fn_hash(
qualifiers.iter().map(|&v| v),
&fn_def.name,
repeat(EMPTY_TYPE_ID()).take(fn_def.params.len()),
);
fn_lib.push((hash_fn_def, fn_def.clone()));
}
}
let mut variables = Vec::new();
let mut functions = Vec::new();
let mut fn_lib = Vec::new();
index_module(
self,
&mut vec!["root"],
&mut variables,
&mut functions,
&mut fn_lib,
);
self.all_variables = variables.into_iter().collect();
self.all_functions = functions.into_iter().collect();
self.all_fn_lib = fn_lib.into();
}
/// Does a type iterator exist in the module?
pub fn contains_iter(&self, id: TypeId) -> bool {
self.type_iterators.contains_key(&id)
}
/// Set a type iterator into the module.
pub fn set_iter(&mut self, typ: TypeId, func: Box<IteratorFn>) {
#[cfg(not(feature = "sync"))]
self.type_iterators.insert(typ, Rc::new(func));
#[cfg(feature = "sync")]
self.type_iterators.insert(typ, Arc::new(func));
}
/// Get the specified type iterator.
pub fn get_iter(&self, id: TypeId) -> Option<&SharedIteratorFunction> {
self.type_iterators.get(&id)
}
}
/// A chain of module names to qualify a variable or function call.
/// A `u64` hash key is kept for quick search purposes.
///
/// A `StaticVec` is used because most module-level access contains only one level,
/// and it is wasteful to always allocate a `Vec` with one element.
#[derive(Clone, Eq, PartialEq, Hash, Default)]
pub struct ModuleRef(StaticVec<(String, Position)>, Option<NonZeroUsize>);
impl fmt::Debug for ModuleRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)?;
if let Some(index) = self.1 {
write!(f, " -> {}", index)
} else {
Ok(())
}
}
}
impl Deref for ModuleRef {
type Target = StaticVec<(String, Position)>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for ModuleRef {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl fmt::Display for ModuleRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (m, _) in self.0.iter() {
write!(f, "{}{}", m, Token::DoubleColon.syntax())?;
}
Ok(())
}
}
impl From<StaticVec<(String, Position)>> for ModuleRef {
fn from(modules: StaticVec<(String, Position)>) -> Self {
Self(modules, None)
}
}
impl ModuleRef {
pub(crate) fn index(&self) -> Option<NonZeroUsize> {
self.1
}
pub(crate) fn set_index(&mut self, index: Option<NonZeroUsize>) {
self.1 = index
}
}
/// A trait that encapsulates a module resolution service.
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "sync"))]
pub trait ModuleResolver {
/// Resolve a module based on a path string.
fn resolve(
&self,
engine: &Engine,
scope: Scope,
path: &str,
pos: Position,
) -> Result<Module, Box<EvalAltResult>>;
}
/// A trait that encapsulates a module resolution service.
#[cfg(not(feature = "no_module"))]
#[cfg(feature = "sync")]
pub trait ModuleResolver: Send + Sync {
/// Resolve a module based on a path string.
fn resolve(
&self,
engine: &Engine,
scope: Scope,
path: &str,
pos: Position,
) -> Result<Module, Box<EvalAltResult>>;
} }
/// Re-export module resolvers. /// Re-export module resolvers.
#[cfg(not(feature = "no_module"))]
pub mod resolvers { pub mod resolvers {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
pub use super::file::FileModuleResolver; pub use super::file::FileModuleResolver;
@ -644,6 +806,7 @@ pub mod resolvers {
} }
/// Script file-based module resolver. /// Script file-based module resolver.
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
mod file { mod file {
use super::*; use super::*;
@ -669,12 +832,18 @@ mod file {
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver)); /// engine.set_module_resolver(Some(resolver));
/// ``` /// ```
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)] #[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Clone, Hash)]
pub struct FileModuleResolver { pub struct FileModuleResolver {
path: PathBuf, path: PathBuf,
extension: String, extension: String,
} }
impl Default for FileModuleResolver {
fn default() -> Self {
Self::new_with_path(PathBuf::default())
}
}
impl FileModuleResolver { impl FileModuleResolver {
/// Create a new `FileModuleResolver` with a specific base path. /// Create a new `FileModuleResolver` with a specific base path.
/// ///
@ -740,12 +909,23 @@ mod file {
pub fn new() -> Self { pub fn new() -> Self {
Default::default() Default::default()
} }
/// Create a `Module` from a file path.
pub fn create_module<P: Into<PathBuf>>(
&self,
engine: &Engine,
scope: Scope,
path: &str,
) -> Result<Module, Box<EvalAltResult>> {
self.resolve(engine, scope, path, Default::default())
}
} }
impl ModuleResolver for FileModuleResolver { impl ModuleResolver for FileModuleResolver {
fn resolve( fn resolve(
&self, &self,
engine: &Engine, engine: &Engine,
scope: Scope,
path: &str, path: &str,
pos: Position, pos: Position,
) -> Result<Module, Box<EvalAltResult>> { ) -> Result<Module, Box<EvalAltResult>> {
@ -757,15 +937,15 @@ mod file {
// Compile it // Compile it
let ast = engine let ast = engine
.compile_file(file_path) .compile_file(file_path)
.map_err(|err| EvalAltResult::set_position(err, pos))?; .map_err(|err| err.new_position(pos))?;
Module::eval_ast_as_new(&ast, engine) Module::eval_ast_as_new(scope, &ast, engine).map_err(|err| err.new_position(pos))
.map_err(|err| EvalAltResult::set_position(err, pos))
} }
} }
} }
/// Static module resolver. /// Static module resolver.
#[cfg(not(feature = "no_module"))]
mod stat { mod stat {
use super::*; use super::*;
@ -828,13 +1008,14 @@ mod stat {
fn resolve( fn resolve(
&self, &self,
_: &Engine, _: &Engine,
_: Scope,
path: &str, path: &str,
pos: Position, pos: Position,
) -> Result<Module, Box<EvalAltResult>> { ) -> Result<Module, Box<EvalAltResult>> {
self.0 self.0
.get(path) .get(path)
.cloned() .cloned()
.ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(path.to_string(), pos))) .ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(path.into(), pos)))
} }
} }
} }

View File

@ -1,10 +1,11 @@
use crate::any::Dynamic; use crate::any::Dynamic;
use crate::calc_fn_hash; use crate::calc_fn_hash;
use crate::engine::{ use crate::engine::{
Engine, FnAny, FnCallArgs, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, Engine, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF,
KEYWORD_TYPE_OF,
}; };
use crate::packages::PackageLibrary; use crate::fn_native::FnCallArgs;
use crate::module::Module;
use crate::packages::PackagesCollection;
use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST}; use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST};
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
@ -13,6 +14,7 @@ use crate::token::Position;
use crate::stdlib::{ use crate::stdlib::{
boxed::Box, boxed::Box,
collections::HashMap, collections::HashMap,
iter::empty,
string::{String, ToString}, string::{String, ToString},
vec, vec,
vec::Vec, vec::Vec,
@ -94,7 +96,7 @@ impl<'a> State<'a> {
} }
/// Add a new constant to the list. /// Add a new constant to the list.
pub fn push_constant(&mut self, name: &str, value: Expr) { pub fn push_constant(&mut self, name: &str, value: Expr) {
self.constants.push((name.to_string(), value)) self.constants.push((name.into(), value))
} }
/// Look up a constant from the list. /// Look up a constant from the list.
pub fn find_constant(&self, name: &str) -> Option<&Expr> { pub fn find_constant(&self, name: &str) -> Option<&Expr> {
@ -110,88 +112,84 @@ impl<'a> State<'a> {
/// Call a registered function /// Call a registered function
fn call_fn( fn call_fn(
packages: &Vec<PackageLibrary>, packages: &PackagesCollection,
functions: &HashMap<u64, Box<FnAny>>, global_module: &Module,
fn_name: &str, fn_name: &str,
args: &mut FnCallArgs, args: &mut FnCallArgs,
pos: Position, pos: Position,
) -> Result<Option<Dynamic>, Box<EvalAltResult>> { ) -> Result<Option<Dynamic>, Box<EvalAltResult>> {
// Search built-in's and external functions // Search built-in's and external functions
let hash = calc_fn_hash(fn_name, args.iter().map(|a| a.type_id())); let hash = calc_fn_hash(empty(), fn_name, args.iter().map(|a| a.type_id()));
functions global_module
.get(&hash) .get_fn(hash)
.or_else(|| { .or_else(|| packages.get_fn(hash))
packages .map(|func| func.call(args))
.iter()
.find(|p| p.functions.contains_key(&hash))
.and_then(|p| p.functions.get(&hash))
})
.map(|func| func(args, pos))
.transpose() .transpose()
.map_err(|err| err.new_position(pos))
} }
/// Optimize a statement. /// Optimize a statement.
fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -> Stmt { fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -> Stmt {
match stmt { match stmt {
// if expr { Noop } // if expr { Noop }
Stmt::IfThenElse(expr, if_block, None) if matches!(*if_block, Stmt::Noop(_)) => { Stmt::IfThenElse(x) if matches!(x.1, Stmt::Noop(_)) => {
state.set_dirty(); state.set_dirty();
let pos = expr.position(); let pos = x.0.position();
let expr = optimize_expr(*expr, state); let expr = optimize_expr(x.0, state);
if preserve_result { if preserve_result {
// -> { expr, Noop } // -> { expr, Noop }
Stmt::Block(vec![Stmt::Expr(Box::new(expr)), *if_block], pos) Stmt::Block(Box::new((vec![Stmt::Expr(Box::new(expr)), x.1], pos)))
} else { } else {
// -> expr // -> expr
Stmt::Expr(Box::new(expr)) Stmt::Expr(Box::new(expr))
} }
} }
// if expr { if_block } // if expr { if_block }
Stmt::IfThenElse(expr, if_block, None) => match *expr { Stmt::IfThenElse(x) if x.2.is_none() => match x.0 {
// if false { if_block } -> Noop // if false { if_block } -> Noop
Expr::False(pos) => { Expr::False(pos) => {
state.set_dirty(); state.set_dirty();
Stmt::Noop(pos) Stmt::Noop(pos)
} }
// if true { if_block } -> if_block // if true { if_block } -> if_block
Expr::True(_) => optimize_stmt(*if_block, state, true), Expr::True(_) => optimize_stmt(x.1, state, true),
// if expr { if_block } // if expr { if_block }
expr => Stmt::IfThenElse( expr => Stmt::IfThenElse(Box::new((
Box::new(optimize_expr(expr, state)), optimize_expr(expr, state),
Box::new(optimize_stmt(*if_block, state, true)), optimize_stmt(x.1, state, true),
None, None,
), ))),
}, },
// if expr { if_block } else { else_block } // if expr { if_block } else { else_block }
Stmt::IfThenElse(expr, if_block, Some(else_block)) => match *expr { Stmt::IfThenElse(x) if x.2.is_some() => match x.0 {
// if false { if_block } else { else_block } -> else_block // if false { if_block } else { else_block } -> else_block
Expr::False(_) => optimize_stmt(*else_block, state, true), Expr::False(_) => optimize_stmt(x.2.unwrap(), state, true),
// if true { if_block } else { else_block } -> if_block // if true { if_block } else { else_block } -> if_block
Expr::True(_) => optimize_stmt(*if_block, state, true), Expr::True(_) => optimize_stmt(x.1, state, true),
// if expr { if_block } else { else_block } // if expr { if_block } else { else_block }
expr => Stmt::IfThenElse( expr => Stmt::IfThenElse(Box::new((
Box::new(optimize_expr(expr, state)), optimize_expr(expr, state),
Box::new(optimize_stmt(*if_block, state, true)), optimize_stmt(x.1, state, true),
match optimize_stmt(*else_block, state, true) { match optimize_stmt(x.2.unwrap(), state, true) {
Stmt::Noop(_) => None, // Noop -> no else block Stmt::Noop(_) => None, // Noop -> no else block
stmt => Some(Box::new(stmt)), stmt => Some(stmt),
}, },
), ))),
}, },
// while expr { block } // while expr { block }
Stmt::While(expr, block) => match *expr { Stmt::While(x) => match x.0 {
// while false { block } -> Noop // while false { block } -> Noop
Expr::False(pos) => { Expr::False(pos) => {
state.set_dirty(); state.set_dirty();
Stmt::Noop(pos) Stmt::Noop(pos)
} }
// while true { block } -> loop { block } // while true { block } -> loop { block }
Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*block, state, false))), Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(x.1, state, false))),
// while expr { block } // while expr { block }
expr => match optimize_stmt(*block, state, false) { expr => match optimize_stmt(x.1, state, false) {
// while expr { break; } -> { expr; } // while expr { break; } -> { expr; }
Stmt::Break(pos) => { Stmt::Break(pos) => {
// Only a single break statement - turn into running the guard expression once // Only a single break statement - turn into running the guard expression once
@ -200,10 +198,10 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
if preserve_result { if preserve_result {
statements.push(Stmt::Noop(pos)) statements.push(Stmt::Noop(pos))
} }
Stmt::Block(statements, pos) Stmt::Block(Box::new((statements, pos)))
} }
// while expr { block } // while expr { block }
stmt => Stmt::While(Box::new(optimize_expr(expr, state)), Box::new(stmt)), stmt => Stmt::While(Box::new((optimize_expr(expr, state), stmt))),
}, },
}, },
// loop { block } // loop { block }
@ -218,38 +216,40 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
stmt => Stmt::Loop(Box::new(stmt)), stmt => Stmt::Loop(Box::new(stmt)),
}, },
// for id in expr { block } // for id in expr { block }
Stmt::For(id, expr, block) => Stmt::For( Stmt::For(x) => Stmt::For(Box::new((
id, x.0,
Box::new(optimize_expr(*expr, state)), optimize_expr(x.1, state),
Box::new(optimize_stmt(*block, state, false)), optimize_stmt(x.2, state, false),
), ))),
// let id = expr; // let id = expr;
Stmt::Let(id, Some(expr), pos) => { Stmt::Let(x) if x.1.is_some() => {
Stmt::Let(id, Some(Box::new(optimize_expr(*expr, state))), pos) Stmt::Let(Box::new((x.0, Some(optimize_expr(x.1.unwrap(), state)))))
} }
// let id; // let id;
Stmt::Let(_, None, _) => stmt, stmt @ Stmt::Let(_) => stmt,
// import expr as id; // import expr as id;
Stmt::Import(expr, id, pos) => Stmt::Import(Box::new(optimize_expr(*expr, state)), id, pos), Stmt::Import(x) => Stmt::Import(Box::new((optimize_expr(x.0, state), x.1))),
// { block } // { block }
Stmt::Block(block, pos) => { Stmt::Block(x) => {
let orig_len = block.len(); // Original number of statements in the block, for change detection let orig_len = x.0.len(); // Original number of statements in the block, for change detection
let orig_constants_len = state.constants.len(); // Original number of constants in the state, for restore later let orig_constants_len = state.constants.len(); // Original number of constants in the state, for restore later
let pos = x.1;
// Optimize each statement in the block // Optimize each statement in the block
let mut result: Vec<_> = block let mut result: Vec<_> =
.into_iter() x.0.into_iter()
.map(|stmt| match stmt { .map(|stmt| match stmt {
// Add constant into the state // Add constant into the state
Stmt::Const(name, value, pos) => { Stmt::Const(v) => {
state.push_constant(&name, *value); let ((name, pos), expr) = *v;
state.set_dirty(); state.push_constant(&name, expr);
Stmt::Noop(pos) // No need to keep constants state.set_dirty();
} Stmt::Noop(pos) // No need to keep constants
// Optimize the statement }
_ => optimize_stmt(stmt, state, preserve_result), // Optimize the statement
}) _ => optimize_stmt(stmt, state, preserve_result),
.collect(); })
.collect();
// Remove all raw expression statements that are pure except for the very last statement // Remove all raw expression statements that are pure except for the very last statement
let last_stmt = if preserve_result { result.pop() } else { None }; let last_stmt = if preserve_result { result.pop() } else { None };
@ -267,9 +267,9 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
while let Some(expr) = result.pop() { while let Some(expr) = result.pop() {
match expr { match expr {
Stmt::Let(_, None, _) => removed = true, Stmt::Let(x) if x.1.is_none() => removed = true,
Stmt::Let(_, Some(val_expr), _) => removed = val_expr.is_pure(), Stmt::Let(x) if x.1.is_some() => removed = x.1.unwrap().is_pure(),
Stmt::Import(expr, _, _) => removed = expr.is_pure(), Stmt::Import(x) => removed = x.0.is_pure(),
_ => { _ => {
result.push(expr); result.push(expr);
break; break;
@ -301,7 +301,7 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
} }
match stmt { match stmt {
Stmt::ReturnWithVal(_, _, _) | Stmt::Break(_) => { Stmt::ReturnWithVal(_) | Stmt::Break(_) => {
dead_code = true; dead_code = true;
} }
_ => (), _ => (),
@ -325,20 +325,20 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
Stmt::Noop(pos) Stmt::Noop(pos)
} }
// Only one let/import statement - leave it alone // Only one let/import statement - leave it alone
[Stmt::Let(_, _, _)] | [Stmt::Import(_, _, _)] => Stmt::Block(result, pos), [Stmt::Let(_)] | [Stmt::Import(_)] => Stmt::Block(Box::new((result, pos))),
// Only one statement - promote // Only one statement - promote
[_] => { [_] => {
state.set_dirty(); state.set_dirty();
result.remove(0) result.remove(0)
} }
_ => Stmt::Block(result, pos), _ => Stmt::Block(Box::new((result, pos))),
} }
} }
// expr; // expr;
Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))), Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))),
// return expr; // return expr;
Stmt::ReturnWithVal(Some(expr), is_return, pos) => { Stmt::ReturnWithVal(x) if x.1.is_some() => {
Stmt::ReturnWithVal(Some(Box::new(optimize_expr(*expr, state))), is_return, pos) Stmt::ReturnWithVal(Box::new((x.0, Some(optimize_expr(x.1.unwrap(), state)))))
} }
// All other statements - skip // All other statements - skip
stmt => stmt, stmt => stmt,
@ -352,11 +352,11 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
match expr { match expr {
// ( stmt ) // ( stmt )
Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, state, true) { Expr::Stmt(x) => match optimize_stmt(x.0, state, true) {
// ( Noop ) -> () // ( Noop ) -> ()
Stmt::Noop(_) => { Stmt::Noop(_) => {
state.set_dirty(); state.set_dirty();
Expr::Unit(pos) Expr::Unit(x.1)
} }
// ( expr ) -> expr // ( expr ) -> expr
Stmt::Expr(expr) => { Stmt::Expr(expr) => {
@ -364,150 +364,129 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
*expr *expr
} }
// ( stmt ) // ( stmt )
stmt => Expr::Stmt(Box::new(stmt), pos), stmt => Expr::Stmt(Box::new((stmt, x.1))),
}, },
// id = expr // id = expr
Expr::Assignment(id, expr, pos) => match *expr { Expr::Assignment(x) => match x.1 {
//id = id2 = expr2 //id = id2 = expr2
Expr::Assignment(id2, expr2, pos2) => match (*id, *id2) { Expr::Assignment(x2) => match (x.0, x2.0) {
// var = var = expr2 -> var = expr2 // var = var = expr2 -> var = expr2
(Expr::Variable(var, None, sp, _), Expr::Variable(var2, None, sp2, _)) (Expr::Variable(a), Expr::Variable(b))
if var == var2 && sp == sp2 => if a.1.is_none() && b.1.is_none() && a.0 == b.0 && a.3 == b.3 =>
{ {
// Assignment to the same variable - fold // Assignment to the same variable - fold
state.set_dirty(); state.set_dirty();
Expr::Assignment(Box::new(Expr::Variable(var, None, sp, pos)), Box::new(optimize_expr(*expr2, state)), pos) Expr::Assignment(Box::new((Expr::Variable(a), optimize_expr(x2.1, state), x.2)))
} }
// id1 = id2 = expr2 // id1 = id2 = expr2
(id1, id2) => Expr::Assignment( (id1, id2) => {
Box::new(id1), Expr::Assignment(Box::new((
Box::new(Expr::Assignment( id1, Expr::Assignment(Box::new((id2, optimize_expr(x2.1, state), x2.2))), x.2,
Box::new(id2), )))
Box::new(optimize_expr(*expr2, state)), }
pos2,
)),
pos,
),
}, },
// id = expr // id = expr
expr => Expr::Assignment(id, Box::new(optimize_expr(expr, state)), pos), expr => Expr::Assignment(Box::new((x.0, optimize_expr(expr, state), x.2))),
}, },
// lhs.rhs // lhs.rhs
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Dot(lhs, rhs, pos) => match (*lhs, *rhs) { Expr::Dot(x) => match (x.0, x.1) {
// map.string // map.string
(Expr::Map(items, pos), Expr::Property(s, _)) if items.iter().all(|(_, x, _)| x.is_pure()) => { (Expr::Map(m), Expr::Property(p)) if m.0.iter().all(|(_, x)| x.is_pure()) => {
let ((prop, _, _), _) = p.as_ref();
// Map literal where everything is pure - promote the indexed item. // Map literal where everything is pure - promote the indexed item.
// All other items can be thrown away. // All other items can be thrown away.
state.set_dirty(); state.set_dirty();
items.into_iter().find(|(name, _, _)| name == &s) let pos = m.1;
.map(|(_, expr, _)| expr.set_position(pos)) m.0.into_iter().find(|((name, _), _)| name == prop)
.map(|(_, expr)| expr.set_position(pos))
.unwrap_or_else(|| Expr::Unit(pos)) .unwrap_or_else(|| Expr::Unit(pos))
} }
// lhs.rhs // lhs.rhs
(lhs, rhs) => Expr::Dot( (lhs, rhs) => Expr::Dot(Box::new((optimize_expr(lhs, state), optimize_expr(rhs, state), x.2)))
Box::new(optimize_expr(lhs, state)),
Box::new(optimize_expr(rhs, state)),
pos,
)
} }
// lhs[rhs] // lhs[rhs]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) { Expr::Index(x) => match (x.0, x.1) {
// array[int] // array[int]
(Expr::Array(mut items, pos), Expr::IntegerConstant(i, _)) (Expr::Array(mut a), Expr::IntegerConstant(i))
if i >= 0 && (i as usize) < items.len() && items.iter().all(Expr::is_pure) => if i.0 >= 0 && (i.0 as usize) < a.0.len() && a.0.iter().all(Expr::is_pure) =>
{ {
// Array literal where everything is pure - promote the indexed item. // Array literal where everything is pure - promote the indexed item.
// All other items can be thrown away. // All other items can be thrown away.
state.set_dirty(); state.set_dirty();
items.remove(i as usize).set_position(pos) a.0.remove(i.0 as usize).set_position(a.1)
} }
// map[string] // map[string]
(Expr::Map(items, pos), Expr::StringConstant(s, _)) if items.iter().all(|(_, x, _)| x.is_pure()) => { (Expr::Map(m), Expr::StringConstant(s)) if m.0.iter().all(|(_, x)| x.is_pure()) => {
// Map literal where everything is pure - promote the indexed item. // Map literal where everything is pure - promote the indexed item.
// All other items can be thrown away. // All other items can be thrown away.
state.set_dirty(); state.set_dirty();
items.into_iter().find(|(name, _, _)| name == &s) let pos = m.1;
.map(|(_, expr, _)| expr.set_position(pos)) m.0.into_iter().find(|((name, _), _)| name == &s.0)
.map(|(_, expr)| expr.set_position(pos))
.unwrap_or_else(|| Expr::Unit(pos)) .unwrap_or_else(|| Expr::Unit(pos))
} }
// string[int] // string[int]
(Expr::StringConstant(s, pos), Expr::IntegerConstant(i, _)) if i >= 0 && (i as usize) < s.chars().count() => { (Expr::StringConstant(s), Expr::IntegerConstant(i)) if i.0 >= 0 && (i.0 as usize) < s.0.chars().count() => {
// String literal indexing - get the character // String literal indexing - get the character
state.set_dirty(); state.set_dirty();
Expr::CharConstant(s.chars().nth(i as usize).expect("should get char"), pos) Expr::CharConstant(Box::new((s.0.chars().nth(i.0 as usize).expect("should get char"), s.1)))
} }
// lhs[rhs] // lhs[rhs]
(lhs, rhs) => Expr::Index( (lhs, rhs) => Expr::Index(Box::new((optimize_expr(lhs, state), optimize_expr(rhs, state), x.2))),
Box::new(optimize_expr(lhs, state)),
Box::new(optimize_expr(rhs, state)),
pos,
),
}, },
// [ items .. ] // [ items .. ]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Array(items, pos) => Expr::Array(items Expr::Array(a) => Expr::Array(Box::new((a.0
.into_iter() .into_iter()
.map(|expr| optimize_expr(expr, state)) .map(|expr| optimize_expr(expr, state))
.collect(), pos), .collect(), a.1))),
// [ items .. ] // [ items .. ]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Map(items, pos) => Expr::Map(items Expr::Map(m) => Expr::Map(Box::new((m.0
.into_iter() .into_iter()
.map(|(key, expr, pos)| (key, optimize_expr(expr, state), pos)) .map(|((key, pos), expr)| ((key, pos), optimize_expr(expr, state)))
.collect(), pos), .collect(), m.1))),
// lhs in rhs // lhs in rhs
Expr::In(lhs, rhs, pos) => match (*lhs, *rhs) { Expr::In(x) => match (x.0, x.1) {
// "xxx" in "xxxxx" // "xxx" in "xxxxx"
(Expr::StringConstant(lhs, pos), Expr::StringConstant(rhs, _)) => { (Expr::StringConstant(a), Expr::StringConstant(b)) => {
state.set_dirty(); state.set_dirty();
if rhs.contains(&lhs) { if b.0.contains(&a.0) { Expr::True(a.1) } else { Expr::False(a.1) }
Expr::True(pos)
} else {
Expr::False(pos)
}
} }
// 'x' in "xxxxx" // 'x' in "xxxxx"
(Expr::CharConstant(lhs, pos), Expr::StringConstant(rhs, _)) => { (Expr::CharConstant(a), Expr::StringConstant(b)) => {
state.set_dirty(); state.set_dirty();
if rhs.contains(&lhs.to_string()) { if b.0.contains(a.0) { Expr::True(a.1) } else { Expr::False(a.1) }
Expr::True(pos)
} else {
Expr::False(pos)
}
} }
// "xxx" in #{...} // "xxx" in #{...}
(Expr::StringConstant(lhs, pos), Expr::Map(items, _)) => { (Expr::StringConstant(a), Expr::Map(b)) => {
state.set_dirty(); state.set_dirty();
if items.iter().find(|(name, _, _)| name == &lhs).is_some() { if b.0.iter().find(|((name, _), _)| name == &a.0).is_some() {
Expr::True(pos) Expr::True(a.1)
} else { } else {
Expr::False(pos) Expr::False(a.1)
} }
} }
// 'x' in #{...} // 'x' in #{...}
(Expr::CharConstant(lhs, pos), Expr::Map(items, _)) => { (Expr::CharConstant(a), Expr::Map(b)) => {
state.set_dirty(); state.set_dirty();
let lhs = lhs.to_string(); let ch = a.0.to_string();
if items.iter().find(|(name, _, _)| name == &lhs).is_some() { if b.0.iter().find(|((name, _), _)| name == &ch).is_some() {
Expr::True(pos) Expr::True(a.1)
} else { } else {
Expr::False(pos) Expr::False(a.1)
} }
} }
// lhs in rhs // lhs in rhs
(lhs, rhs) => Expr::In( (lhs, rhs) => Expr::In(Box::new((optimize_expr(lhs, state), optimize_expr(rhs, state), x.2))),
Box::new(optimize_expr(lhs, state)),
Box::new(optimize_expr(rhs, state)),
pos
),
}, },
// lhs && rhs // lhs && rhs
Expr::And(lhs, rhs, pos) => match (*lhs, *rhs) { Expr::And(x) => match (x.0, x.1) {
// true && rhs -> rhs // true && rhs -> rhs
(Expr::True(_), rhs) => { (Expr::True(_), rhs) => {
state.set_dirty(); state.set_dirty();
@ -524,14 +503,10 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
optimize_expr(lhs, state) optimize_expr(lhs, state)
} }
// lhs && rhs // lhs && rhs
(lhs, rhs) => Expr::And( (lhs, rhs) => Expr::And(Box::new((optimize_expr(lhs, state), optimize_expr(rhs, state), x.2))),
Box::new(optimize_expr(lhs, state)),
Box::new(optimize_expr(rhs, state)),
pos
),
}, },
// lhs || rhs // lhs || rhs
Expr::Or(lhs, rhs, pos) => match (*lhs, *rhs) { Expr::Or(x) => match (x.0, x.1) {
// false || rhs -> rhs // false || rhs -> rhs
(Expr::False(_), rhs) => { (Expr::False(_), rhs) => {
state.set_dirty(); state.set_dirty();
@ -548,22 +523,28 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
optimize_expr(lhs, state) optimize_expr(lhs, state)
} }
// lhs || rhs // lhs || rhs
(lhs, rhs) => Expr::Or(Box::new(optimize_expr(lhs, state)), Box::new(optimize_expr(rhs, state)), pos), (lhs, rhs) => Expr::Or(Box::new((optimize_expr(lhs, state), optimize_expr(rhs, state), x.2))),
}, },
// Do not call some special keywords // Do not call some special keywords
Expr::FnCall(id, None, args, def_value, pos) if DONT_EVAL_KEYWORDS.contains(&id.as_ref().as_ref())=> Expr::FnCall(mut x) if DONT_EVAL_KEYWORDS.contains(&(x.0).0.as_ref())=> {
Expr::FnCall(id, None, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos), x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
Expr::FnCall(x)
}
// Eagerly call functions // Eagerly call functions
Expr::FnCall(id, None, args, def_value, pos) Expr::FnCall(mut x)
if state.optimization_level == OptimizationLevel::Full // full optimizations if x.1.is_none() // Non-qualified
&& args.iter().all(|expr| expr.is_constant()) // all arguments are constants && state.optimization_level == OptimizationLevel::Full // full optimizations
&& x.3.iter().all(|expr| expr.is_constant()) // all arguments are constants
=> { => {
let ((name, pos), _, _, args, def_value) = x.as_mut();
// First search in script-defined functions (can override built-in) // 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() { if state.fn_lib.iter().find(|(id, len)| *id == name && *len == args.len()).is_some() {
// A script-defined function overrides the built-in function - do not make the call // A script-defined function overrides the built-in function - do not make the call
return Expr::FnCall(id, None, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos); x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
return Expr::FnCall(x);
} }
let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect(); let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect();
@ -571,13 +552,13 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
// Save the typename of the first argument if it is `type_of()` // Save the typename of the first argument if it is `type_of()`
// This is to avoid `call_args` being passed into the closure // This is to avoid `call_args` being passed into the closure
let arg_for_type_of = if *id == KEYWORD_TYPE_OF && call_args.len() == 1 { let arg_for_type_of = if name == KEYWORD_TYPE_OF && call_args.len() == 1 {
state.engine.map_type_name(call_args[0].type_name()) state.engine.map_type_name(call_args[0].type_name())
} else { } else {
"" ""
}; };
call_fn(&state.engine.packages, &state.engine.functions, &id, &mut call_args, pos).ok() call_fn(&state.engine.packages, &state.engine.global_module, name, &mut call_args, *pos).ok()
.and_then(|result| .and_then(|result|
result.or_else(|| { result.or_else(|| {
if !arg_for_type_of.is_empty() { if !arg_for_type_of.is_empty() {
@ -585,25 +566,29 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
Some(arg_for_type_of.to_string().into()) Some(arg_for_type_of.to_string().into())
} else { } else {
// Otherwise use the default value, if any // Otherwise use the default value, if any
def_value.clone().map(|v| *v) def_value.clone()
} }
}).and_then(|result| map_dynamic_to_expr(result, pos)) }).and_then(|result| map_dynamic_to_expr(result, *pos))
.map(|expr| { .map(|expr| {
state.set_dirty(); state.set_dirty();
expr expr
}) })
).unwrap_or_else(|| ).unwrap_or_else(|| {
// Optimize function call arguments // Optimize function call arguments
Expr::FnCall(id, None, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos) x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
) Expr::FnCall(x)
})
} }
// id(args ..) -> optimize function call arguments // id(args ..) -> optimize function call arguments
Expr::FnCall(id, modules, args, def_value, pos) => Expr::FnCall(mut x) => {
Expr::FnCall(id, modules, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos), x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
Expr::FnCall(x)
}
// constant-name // constant-name
Expr::Variable(name, None, _, pos) if state.contains_constant(&name) => { Expr::Variable(x) if x.1.is_none() && state.contains_constant(&(x.0).0) => {
let (name, pos) = x.0;
state.set_dirty(); state.set_dirty();
// Replace constant with value // Replace constant with value
@ -660,17 +645,18 @@ fn optimize<'a>(
.into_iter() .into_iter()
.enumerate() .enumerate()
.map(|(i, stmt)| { .map(|(i, stmt)| {
match stmt { match &stmt {
Stmt::Const(ref name, ref value, _) => { Stmt::Const(v) => {
// Load constants // Load constants
state.push_constant(name.as_ref(), value.as_ref().clone()); let ((name, _), expr) = v.as_ref();
state.push_constant(&name, expr.clone());
stmt // Keep it in the global scope stmt // Keep it in the global scope
} }
_ => { _ => {
// Keep all variable declarations at this level // Keep all variable declarations at this level
// and always keep the last return value // and always keep the last return value
let keep = match stmt { let keep = match stmt {
Stmt::Let(_, _, _) | Stmt::Import(_, _, _) => true, Stmt::Let(_) | Stmt::Import(_) => true,
_ => i == num_statements - 1, _ => i == num_statements - 1,
}; };
optimize_stmt(stmt, &mut state, keep) optimize_stmt(stmt, &mut state, keep)
@ -721,34 +707,29 @@ pub fn optimize_into_ast(
const fn_lib: &[(&str, usize)] = &[]; const fn_lib: &[(&str, usize)] = &[];
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
let lib = FunctionsLib::from_vec( let lib = FunctionsLib::from_iter(functions.iter().cloned().map(|mut fn_def| {
functions if !level.is_none() {
.iter() let pos = fn_def.body.position();
.cloned()
.map(|mut fn_def| {
if !level.is_none() {
let pos = fn_def.body.position();
// Optimize the function body // Optimize the function body
let mut body = let mut body = optimize(vec![fn_def.body], engine, &Scope::new(), &fn_lib, level);
optimize(vec![*fn_def.body], engine, &Scope::new(), &fn_lib, level);
// {} -> Noop // {} -> Noop
fn_def.body = Box::new(match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) { fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
// { return val; } -> val // { return val; } -> val
Stmt::ReturnWithVal(Some(val), ReturnType::Return, _) => Stmt::Expr(val), Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => {
// { return; } -> () Stmt::Expr(Box::new(x.1.unwrap()))
Stmt::ReturnWithVal(None, ReturnType::Return, pos) => {
Stmt::Expr(Box::new(Expr::Unit(pos)))
}
// All others
stmt => stmt,
});
} }
fn_def // { return; } -> ()
}) Stmt::ReturnWithVal(x) if x.1.is_none() && (x.0).0 == ReturnType::Return => {
.collect(), Stmt::Expr(Box::new(Expr::Unit((x.0).1)))
); }
// All others
stmt => stmt,
};
}
fn_def
}));
#[cfg(feature = "no_function")] #[cfg(feature = "no_function")]
let lib: FunctionsLib = Default::default(); let lib: FunctionsLib = Default::default();

View File

@ -1,7 +1,5 @@
use super::{reg_binary, reg_unary};
use crate::def_package; use crate::def_package;
use crate::fn_register::{map_dynamic as map, map_result as result}; use crate::module::FuncReturn;
use crate::parser::INT; use crate::parser::INT;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::token::Position; use crate::token::Position;
@ -22,7 +20,7 @@ use crate::stdlib::{
}; };
// Checked add // Checked add
fn add<T: Display + CheckedAdd>(x: T, y: T) -> Result<T, Box<EvalAltResult>> { fn add<T: Display + CheckedAdd>(x: T, y: T) -> FuncReturn<T> {
x.checked_add(&y).ok_or_else(|| { x.checked_add(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Addition overflow: {} + {}", x, y), format!("Addition overflow: {} + {}", x, y),
@ -31,7 +29,7 @@ fn add<T: Display + CheckedAdd>(x: T, y: T) -> Result<T, Box<EvalAltResult>> {
}) })
} }
// Checked subtract // Checked subtract
fn sub<T: Display + CheckedSub>(x: T, y: T) -> Result<T, Box<EvalAltResult>> { fn sub<T: Display + CheckedSub>(x: T, y: T) -> FuncReturn<T> {
x.checked_sub(&y).ok_or_else(|| { x.checked_sub(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Subtraction underflow: {} - {}", x, y), format!("Subtraction underflow: {} - {}", x, y),
@ -40,7 +38,7 @@ fn sub<T: Display + CheckedSub>(x: T, y: T) -> Result<T, Box<EvalAltResult>> {
}) })
} }
// Checked multiply // Checked multiply
fn mul<T: Display + CheckedMul>(x: T, y: T) -> Result<T, Box<EvalAltResult>> { fn mul<T: Display + CheckedMul>(x: T, y: T) -> FuncReturn<T> {
x.checked_mul(&y).ok_or_else(|| { x.checked_mul(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Multiplication overflow: {} * {}", x, y), format!("Multiplication overflow: {} * {}", x, y),
@ -49,7 +47,7 @@ fn mul<T: Display + CheckedMul>(x: T, y: T) -> Result<T, Box<EvalAltResult>> {
}) })
} }
// Checked divide // Checked divide
fn div<T>(x: T, y: T) -> Result<T, Box<EvalAltResult>> fn div<T>(x: T, y: T) -> FuncReturn<T>
where where
T: Display + CheckedDiv + PartialEq + Zero, T: Display + CheckedDiv + PartialEq + Zero,
{ {
@ -69,7 +67,7 @@ where
}) })
} }
// Checked negative - e.g. -(i32::MIN) will overflow i32::MAX // Checked negative - e.g. -(i32::MIN) will overflow i32::MAX
fn neg<T: Display + CheckedNeg>(x: T) -> Result<T, Box<EvalAltResult>> { fn neg<T: Display + CheckedNeg>(x: T) -> FuncReturn<T> {
x.checked_neg().ok_or_else(|| { x.checked_neg().ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Negation overflow: -{}", x), format!("Negation overflow: -{}", x),
@ -78,7 +76,7 @@ fn neg<T: Display + CheckedNeg>(x: T) -> Result<T, Box<EvalAltResult>> {
}) })
} }
// Checked absolute // Checked absolute
fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> Result<T, Box<EvalAltResult>> { fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> FuncReturn<T> {
// FIX - We don't use Signed::abs() here because, contrary to documentation, it panics // FIX - We don't use Signed::abs() here because, contrary to documentation, it panics
// when the number is ::MIN instead of returning ::MIN itself. // when the number is ::MIN instead of returning ::MIN itself.
if x >= <T as Zero>::zero() { if x >= <T as Zero>::zero() {
@ -93,49 +91,49 @@ fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> Result<T, Box<EvalA
} }
} }
// Unchecked add - may panic on overflow // Unchecked add - may panic on overflow
fn add_u<T: Add>(x: T, y: T) -> <T as Add>::Output { fn add_u<T: Add>(x: T, y: T) -> FuncReturn<<T as Add>::Output> {
x + y Ok(x + y)
} }
// Unchecked subtract - may panic on underflow // Unchecked subtract - may panic on underflow
fn sub_u<T: Sub>(x: T, y: T) -> <T as Sub>::Output { fn sub_u<T: Sub>(x: T, y: T) -> FuncReturn<<T as Sub>::Output> {
x - y Ok(x - y)
} }
// Unchecked multiply - may panic on overflow // Unchecked multiply - may panic on overflow
fn mul_u<T: Mul>(x: T, y: T) -> <T as Mul>::Output { fn mul_u<T: Mul>(x: T, y: T) -> FuncReturn<<T as Mul>::Output> {
x * y Ok(x * y)
} }
// Unchecked divide - may panic when dividing by zero // Unchecked divide - may panic when dividing by zero
fn div_u<T: Div>(x: T, y: T) -> <T as Div>::Output { fn div_u<T: Div>(x: T, y: T) -> FuncReturn<<T as Div>::Output> {
x / y Ok(x / y)
} }
// Unchecked negative - may panic on overflow // Unchecked negative - may panic on overflow
fn neg_u<T: Neg>(x: T) -> <T as Neg>::Output { fn neg_u<T: Neg>(x: T) -> FuncReturn<<T as Neg>::Output> {
-x Ok(-x)
} }
// Unchecked absolute - may panic on overflow // Unchecked absolute - may panic on overflow
fn abs_u<T>(x: T) -> <T as Neg>::Output fn abs_u<T>(x: T) -> FuncReturn<<T as Neg>::Output>
where where
T: Neg + PartialOrd + Default + Into<<T as Neg>::Output>, T: Neg + PartialOrd + Default + Into<<T as Neg>::Output>,
{ {
// Numbers should default to zero // Numbers should default to zero
if x < Default::default() { if x < Default::default() {
-x Ok(-x)
} else { } else {
x.into() Ok(x.into())
} }
} }
// Bit operators // Bit operators
fn binary_and<T: BitAnd>(x: T, y: T) -> <T as BitAnd>::Output { fn binary_and<T: BitAnd>(x: T, y: T) -> FuncReturn<<T as BitAnd>::Output> {
x & y Ok(x & y)
} }
fn binary_or<T: BitOr>(x: T, y: T) -> <T as BitOr>::Output { fn binary_or<T: BitOr>(x: T, y: T) -> FuncReturn<<T as BitOr>::Output> {
x | y Ok(x | y)
} }
fn binary_xor<T: BitXor>(x: T, y: T) -> <T as BitXor>::Output { fn binary_xor<T: BitXor>(x: T, y: T) -> FuncReturn<<T as BitXor>::Output> {
x ^ y Ok(x ^ y)
} }
// Checked left-shift // Checked left-shift
fn shl<T: Display + CheckedShl>(x: T, y: INT) -> Result<T, Box<EvalAltResult>> { fn shl<T: Display + CheckedShl>(x: T, y: INT) -> FuncReturn<T> {
// Cannot shift by a negative number of bits // Cannot shift by a negative number of bits
if y < 0 { if y < 0 {
return Err(Box::new(EvalAltResult::ErrorArithmetic( return Err(Box::new(EvalAltResult::ErrorArithmetic(
@ -152,7 +150,7 @@ fn shl<T: Display + CheckedShl>(x: T, y: INT) -> Result<T, Box<EvalAltResult>> {
}) })
} }
// Checked right-shift // Checked right-shift
fn shr<T: Display + CheckedShr>(x: T, y: INT) -> Result<T, Box<EvalAltResult>> { fn shr<T: Display + CheckedShr>(x: T, y: INT) -> FuncReturn<T> {
// Cannot shift by a negative number of bits // Cannot shift by a negative number of bits
if y < 0 { if y < 0 {
return Err(Box::new(EvalAltResult::ErrorArithmetic( return Err(Box::new(EvalAltResult::ErrorArithmetic(
@ -169,15 +167,15 @@ fn shr<T: Display + CheckedShr>(x: T, y: INT) -> Result<T, Box<EvalAltResult>> {
}) })
} }
// Unchecked left-shift - may panic if shifting by a negative number of bits // Unchecked left-shift - may panic if shifting by a negative number of bits
fn shl_u<T: Shl<T>>(x: T, y: T) -> <T as Shl<T>>::Output { fn shl_u<T: Shl<T>>(x: T, y: T) -> FuncReturn<<T as Shl<T>>::Output> {
x.shl(y) Ok(x.shl(y))
} }
// Unchecked right-shift - may panic if shifting by a negative number of bits // Unchecked right-shift - may panic if shifting by a negative number of bits
fn shr_u<T: Shr<T>>(x: T, y: T) -> <T as Shr<T>>::Output { fn shr_u<T: Shr<T>>(x: T, y: T) -> FuncReturn<<T as Shr<T>>::Output> {
x.shr(y) Ok(x.shr(y))
} }
// Checked modulo // Checked modulo
fn modulo<T: Display + CheckedRem>(x: T, y: T) -> Result<T, Box<EvalAltResult>> { fn modulo<T: Display + CheckedRem>(x: T, y: T) -> FuncReturn<T> {
x.checked_rem(&y).ok_or_else(|| { x.checked_rem(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Modulo division by zero or overflow: {} % {}", x, y), format!("Modulo division by zero or overflow: {} % {}", x, y),
@ -186,11 +184,11 @@ fn modulo<T: Display + CheckedRem>(x: T, y: T) -> Result<T, Box<EvalAltResult>>
}) })
} }
// Unchecked modulo - may panic if dividing by zero // Unchecked modulo - may panic if dividing by zero
fn modulo_u<T: Rem>(x: T, y: T) -> <T as Rem>::Output { fn modulo_u<T: Rem>(x: T, y: T) -> FuncReturn<<T as Rem>::Output> {
x % y Ok(x % y)
} }
// Checked power // Checked power
fn pow_i_i(x: INT, y: INT) -> Result<INT, Box<EvalAltResult>> { fn pow_i_i(x: INT, y: INT) -> FuncReturn<INT> {
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
{ {
if y > (u32::MAX as INT) { if y > (u32::MAX as INT) {
@ -231,17 +229,17 @@ fn pow_i_i(x: INT, y: INT) -> Result<INT, Box<EvalAltResult>> {
} }
} }
// Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX) // Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX)
fn pow_i_i_u(x: INT, y: INT) -> INT { fn pow_i_i_u(x: INT, y: INT) -> FuncReturn<INT> {
x.pow(y as u32) Ok(x.pow(y as u32))
} }
// Floating-point power - always well-defined // Floating-point power - always well-defined
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
fn pow_f_f(x: FLOAT, y: FLOAT) -> FLOAT { fn pow_f_f(x: FLOAT, y: FLOAT) -> FuncReturn<FLOAT> {
x.powf(y) Ok(x.powf(y))
} }
// Checked power // Checked power
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
fn pow_f_i(x: FLOAT, y: INT) -> Result<FLOAT, Box<EvalAltResult>> { fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn<FLOAT> {
// Raise to power that is larger than an i32 // Raise to power that is larger than an i32
if y > (i32::MAX as INT) { if y > (i32::MAX as INT) {
return Err(Box::new(EvalAltResult::ErrorArithmetic( return Err(Box::new(EvalAltResult::ErrorArithmetic(
@ -255,39 +253,37 @@ fn pow_f_i(x: FLOAT, y: INT) -> Result<FLOAT, Box<EvalAltResult>> {
// Unchecked power - may be incorrect if the power index is too high (> i32::MAX) // Unchecked power - may be incorrect if the power index is too high (> i32::MAX)
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
fn pow_f_i_u(x: FLOAT, y: INT) -> FLOAT { fn pow_f_i_u(x: FLOAT, y: INT) -> FuncReturn<FLOAT> {
x.powi(y as i32) Ok(x.powi(y as i32))
} }
macro_rules! reg_unary_x { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { macro_rules! reg_unary {
$(reg_unary($lib, $op, $func::<$par>, result);)* }; ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
$( $lib.set_fn_1($op, $func::<$par>); )*
};
} }
macro_rules! reg_unary { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { macro_rules! reg_op {
$(reg_unary($lib, $op, $func::<$par>, map);)* }; ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
} $( $lib.set_fn_2($op, $func::<$par>); )*
macro_rules! reg_op_x { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { };
$(reg_binary($lib, $op, $func::<$par>, result);)* };
}
macro_rules! reg_op { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
$(reg_binary($lib, $op, $func::<$par>, map);)* };
} }
def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
// Checked basic arithmetic // Checked basic arithmetic
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
{ {
reg_op_x!(lib, "+", add, INT); reg_op!(lib, "+", add, INT);
reg_op_x!(lib, "-", sub, INT); reg_op!(lib, "-", sub, INT);
reg_op_x!(lib, "*", mul, INT); reg_op!(lib, "*", mul, INT);
reg_op_x!(lib, "/", div, INT); reg_op!(lib, "/", div, INT);
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
reg_op_x!(lib, "+", add, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "+", add, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op_x!(lib, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op_x!(lib, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op_x!(lib, "/", div, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "/", div, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
} }
} }
@ -334,16 +330,16 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
// Checked bit shifts // Checked bit shifts
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
{ {
reg_op_x!(lib, "<<", shl, INT); reg_op!(lib, "<<", shl, INT);
reg_op_x!(lib, ">>", shr, INT); reg_op!(lib, ">>", shr, INT);
reg_op_x!(lib, "%", modulo, INT); reg_op!(lib, "%", modulo, INT);
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
reg_op_x!(lib, "<<", shl, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "<<", shl, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op_x!(lib, ">>", shr, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, ">>", shr, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op_x!(lib, "%", modulo, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "%", modulo, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
} }
} }
@ -366,39 +362,39 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
// Checked power // Checked power
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
{ {
reg_binary(lib, "~", pow_i_i, result); lib.set_fn_2("~", pow_i_i);
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
reg_binary(lib, "~", pow_f_i, result); lib.set_fn_2("~", pow_f_i);
} }
// Unchecked power // Unchecked power
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]
{ {
reg_binary(lib, "~", pow_i_i_u, map); lib.set_fn_2("~", pow_i_i_u);
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
reg_binary(lib, "~", pow_f_i_u, map); lib.set_fn_2("~", pow_f_i_u);
} }
// Floating-point modulo and power // Floating-point modulo and power
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
{ {
reg_op!(lib, "%", modulo_u, f32, f64); reg_op!(lib, "%", modulo_u, f32, f64);
reg_binary(lib, "~", pow_f_f, map); lib.set_fn_2("~", pow_f_f);
} }
// Checked unary // Checked unary
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
{ {
reg_unary_x!(lib, "-", neg, INT); reg_unary!(lib, "-", neg, INT);
reg_unary_x!(lib, "abs", abs, INT); reg_unary!(lib, "abs", abs, INT);
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
reg_unary_x!(lib, "-", neg, i8, i16, i32, i64, i128); reg_unary!(lib, "-", neg, i8, i16, i32, i64, i128);
reg_unary_x!(lib, "abs", abs, i8, i16, i32, i64, i128); reg_unary!(lib, "abs", abs, i8, i16, i32, i64, i128);
} }
} }

View File

@ -1,41 +1,46 @@
#![cfg(not(feature = "no_index"))] #![cfg(not(feature = "no_index"))]
use super::{reg_binary, reg_binary_mut, reg_trinary_mut, reg_unary_mut};
use crate::any::{Dynamic, Variant}; use crate::any::{Dynamic, Variant};
use crate::def_package; use crate::def_package;
use crate::engine::Array; use crate::engine::Array;
use crate::fn_register::{map_dynamic as map, map_identity as pass}; use crate::module::FuncReturn;
use crate::parser::INT; use crate::parser::INT;
use crate::stdlib::{any::TypeId, boxed::Box, string::String}; use crate::stdlib::{any::TypeId, boxed::Box, string::String};
// Register array utility functions // Register array utility functions
fn push<T: Variant + Clone>(list: &mut Array, item: T) { fn push<T: Variant + Clone>(list: &mut Array, item: T) -> FuncReturn<()> {
list.push(Dynamic::from(item)); list.push(Dynamic::from(item));
Ok(())
} }
fn ins<T: Variant + Clone>(list: &mut Array, position: INT, item: T) { fn ins<T: Variant + Clone>(list: &mut Array, position: INT, item: T) -> FuncReturn<()> {
if position <= 0 { if position <= 0 {
list.insert(0, Dynamic::from(item)); list.insert(0, Dynamic::from(item));
} else if (position as usize) >= list.len() - 1 { } else if (position as usize) >= list.len() - 1 {
push(list, item); push(list, item)?;
} else { } else {
list.insert(position as usize, Dynamic::from(item)); list.insert(position as usize, Dynamic::from(item));
} }
Ok(())
} }
fn pad<T: Variant + Clone>(list: &mut Array, len: INT, item: T) { fn pad<T: Variant + Clone>(list: &mut Array, len: INT, item: T) -> FuncReturn<()> {
if len >= 0 { if len >= 0 {
while list.len() < len as usize { while list.len() < len as usize {
push(list, item.clone()); push(list, item.clone())?;
} }
} }
Ok(())
} }
macro_rules! reg_op { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { macro_rules! reg_op {
$(reg_binary_mut($lib, $op, $func::<$par>, map);)* }; ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
$( $lib.set_fn_2_mut($op, $func::<$par>); )*
};
} }
macro_rules! reg_tri { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { macro_rules! reg_tri {
$(reg_trinary_mut($lib, $op, $func::<$par>, map);)* }; ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
$( $lib.set_fn_3_mut($op, $func::<$par>); )*
};
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -44,15 +49,16 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
reg_tri!(lib, "pad", pad, INT, bool, char, String, Array, ()); reg_tri!(lib, "pad", pad, INT, bool, char, String, Array, ());
reg_tri!(lib, "insert", ins, INT, bool, char, String, Array, ()); reg_tri!(lib, "insert", ins, INT, bool, char, String, Array, ());
reg_binary_mut(lib, "append", |x: &mut Array, y: Array| x.extend(y), map); lib.set_fn_2_mut("append", |x: &mut Array, y: Array| {
reg_binary( x.extend(y);
lib, Ok(())
});
lib.set_fn_2(
"+", "+",
|mut x: Array, y: Array| { |mut x: Array, y: Array| {
x.extend(y); x.extend(y);
x Ok(x)
}, },
map,
); );
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
@ -70,40 +76,36 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
reg_tri!(lib, "insert", ins, f32, f64); reg_tri!(lib, "insert", ins, f32, f64);
} }
reg_unary_mut( lib.set_fn_1_mut(
lib,
"pop", "pop",
|list: &mut Array| list.pop().unwrap_or_else(|| ().into()), |list: &mut Array| Ok(list.pop().unwrap_or_else(|| ().into())),
pass,
); );
reg_unary_mut( lib.set_fn_1_mut(
lib,
"shift", "shift",
|list: &mut Array| { |list: &mut Array| {
if list.is_empty() { Ok(if list.is_empty() {
().into() ().into()
} else { } else {
list.remove(0) list.remove(0)
} })
}, },
pass,
); );
reg_binary_mut( lib.set_fn_2_mut(
lib,
"remove", "remove",
|list: &mut Array, len: INT| { |list: &mut Array, len: INT| {
if len < 0 || (len as usize) >= list.len() { Ok(if len < 0 || (len as usize) >= list.len() {
().into() ().into()
} else { } else {
list.remove(len as usize) list.remove(len as usize)
} })
}, },
pass,
); );
reg_unary_mut(lib, "len", |list: &mut Array| list.len() as INT, map); lib.set_fn_1_mut("len", |list: &mut Array| Ok(list.len() as INT));
reg_unary_mut(lib, "clear", |list: &mut Array| list.clear(), map); lib.set_fn_1_mut("clear", |list: &mut Array| {
reg_binary_mut( list.clear();
lib, Ok(())
});
lib.set_fn_2_mut(
"truncate", "truncate",
|list: &mut Array, len: INT| { |list: &mut Array, len: INT| {
if len >= 0 { if len >= 0 {
@ -111,16 +113,15 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
} else { } else {
list.clear(); list.clear();
} }
Ok(())
}, },
map,
); );
// Register array iterator // Register array iterator
lib.type_iterators.insert( lib.set_iter(
TypeId::of::<Array>(), TypeId::of::<Array>(),
Box::new(|a: Dynamic| { Box::new(|arr| Box::new(
Box::new(a.cast::<Array>().into_iter()) arr.cast::<Array>().into_iter()) as Box<dyn Iterator<Item = Dynamic>>
as Box<dyn Iterator<Item = Dynamic>> ),
}),
); );
}); });

View File

@ -1,8 +1,6 @@
use super::{reg_binary, reg_trinary, PackageStore};
use crate::any::{Dynamic, Variant}; use crate::any::{Dynamic, Variant};
use crate::def_package; use crate::def_package;
use crate::fn_register::map_dynamic as map; use crate::module::{FuncReturn, Module};
use crate::parser::INT; use crate::parser::INT;
use crate::stdlib::{ use crate::stdlib::{
@ -12,19 +10,23 @@ use crate::stdlib::{
}; };
// Register range function // Register range function
fn reg_range<T: Variant + Clone>(lib: &mut PackageStore) fn reg_range<T: Variant + Clone>(lib: &mut Module)
where where
Range<T>: Iterator<Item = T>, Range<T>: Iterator<Item = T>,
{ {
lib.type_iterators.insert( lib.set_iter(
TypeId::of::<Range<T>>(), TypeId::of::<Range<T>>(),
Box::new(|source: Dynamic| { Box::new(|source| {
Box::new(source.cast::<Range<T>>().map(|x| x.into_dynamic())) Box::new(source.cast::<Range<T>>().map(|x| x.into_dynamic()))
as Box<dyn Iterator<Item = Dynamic>> as Box<dyn Iterator<Item = Dynamic>>
}), }),
); );
} }
fn get_range<T: Variant + Clone>(from: T, to: T) -> FuncReturn<Range<T>> {
Ok(from..to)
}
// Register range function with step // Register range function with step
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
struct StepRange<T>(T, T, T) struct StepRange<T>(T, T, T)
@ -50,37 +52,41 @@ where
} }
} }
fn reg_step<T>(lib: &mut PackageStore) fn reg_step<T>(lib: &mut Module)
where where
for<'a> &'a T: Add<&'a T, Output = T>, for<'a> &'a T: Add<&'a T, Output = T>,
T: Variant + Clone + PartialOrd, T: Variant + Clone + PartialOrd,
StepRange<T>: Iterator<Item = T>, StepRange<T>: Iterator<Item = T>,
{ {
lib.type_iterators.insert( lib.set_iter(
TypeId::of::<StepRange<T>>(), TypeId::of::<StepRange<T>>(),
Box::new(|source: Dynamic| { Box::new(|source| {
Box::new(source.cast::<StepRange<T>>().map(|x| x.into_dynamic())) Box::new(source.cast::<StepRange<T>>().map(|x| x.into_dynamic()))
as Box<dyn Iterator<Item = Dynamic>> as Box<dyn Iterator<Item = Dynamic>>
}), }),
); );
} }
def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { fn get_step_range<T>(from: T, to: T, step: T) -> FuncReturn<StepRange<T>>
fn get_range<T>(from: T, to: T) -> Range<T> { where
from..to for<'a> &'a T: Add<&'a T, Output = T>,
} T: Variant + Clone + PartialOrd,
{
Ok(StepRange::<T>(from, to, step))
}
def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
reg_range::<INT>(lib); reg_range::<INT>(lib);
reg_binary(lib, "range", get_range::<INT>, map); lib.set_fn_2("range", get_range::<INT>);
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
macro_rules! reg_range { macro_rules! reg_range {
($self:expr, $x:expr, $( $y:ty ),*) => ( ($lib:expr, $x:expr, $( $y:ty ),*) => (
$( $(
reg_range::<$y>($self); reg_range::<$y>($lib);
reg_binary($self, $x, get_range::<$y>, map); $lib.set_fn_2($x, get_range::<$y>);
)* )*
) )
} }
@ -89,16 +95,16 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
} }
reg_step::<INT>(lib); reg_step::<INT>(lib);
reg_trinary(lib, "range", StepRange::<INT>, map); lib.set_fn_3("range", get_step_range::<INT>);
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
macro_rules! reg_step { macro_rules! reg_step {
($self:expr, $x:expr, $( $y:ty ),*) => ( ($lib:expr, $x:expr, $( $y:ty ),*) => (
$( $(
reg_step::<$y>($self); reg_step::<$y>($lib);
reg_trinary($self, $x, StepRange::<$y>, map); $lib.set_fn_3($x, get_step_range::<$y>);
)* )*
) )
} }

View File

@ -1,44 +1,44 @@
use super::{reg_binary, reg_binary_mut, reg_unary};
use crate::def_package; use crate::def_package;
use crate::fn_register::map_dynamic as map; use crate::module::FuncReturn;
use crate::parser::INT; use crate::parser::INT;
use crate::stdlib::string::String; use crate::stdlib::string::String;
// Comparison operators // Comparison operators
pub fn lt<T: PartialOrd>(x: T, y: T) -> bool { pub fn lt<T: PartialOrd>(x: T, y: T) -> FuncReturn<bool> {
x < y Ok(x < y)
} }
pub fn lte<T: PartialOrd>(x: T, y: T) -> bool { pub fn lte<T: PartialOrd>(x: T, y: T) -> FuncReturn<bool> {
x <= y Ok(x <= y)
} }
pub fn gt<T: PartialOrd>(x: T, y: T) -> bool { pub fn gt<T: PartialOrd>(x: T, y: T) -> FuncReturn<bool> {
x > y Ok(x > y)
} }
pub fn gte<T: PartialOrd>(x: T, y: T) -> bool { pub fn gte<T: PartialOrd>(x: T, y: T) -> FuncReturn<bool> {
x >= y Ok(x >= y)
} }
pub fn eq<T: PartialEq>(x: T, y: T) -> bool { pub fn eq<T: PartialEq>(x: T, y: T) -> FuncReturn<bool> {
x == y Ok(x == y)
} }
pub fn ne<T: PartialEq>(x: T, y: T) -> bool { pub fn ne<T: PartialEq>(x: T, y: T) -> FuncReturn<bool> {
x != y Ok(x != y)
} }
// Logic operators // Logic operators
fn and(x: bool, y: bool) -> bool { fn and(x: bool, y: bool) -> FuncReturn<bool> {
x && y Ok(x && y)
} }
fn or(x: bool, y: bool) -> bool { fn or(x: bool, y: bool) -> FuncReturn<bool> {
x || y Ok(x || y)
} }
fn not(x: bool) -> bool { fn not(x: bool) -> FuncReturn<bool> {
!x Ok(!x)
} }
macro_rules! reg_op { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { macro_rules! reg_op {
$(reg_binary($lib, $op, $func::<$par>, map);)* }; ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
$( $lib.set_fn_2($op, $func::<$par>); )*
};
} }
def_package!(crate:LogicPackage:"Logical operators.", lib, { def_package!(crate:LogicPackage:"Logical operators.", lib, {
@ -50,14 +50,12 @@ def_package!(crate:LogicPackage:"Logical operators.", lib, {
reg_op!(lib, "!=", ne, INT, char, bool, ()); reg_op!(lib, "!=", ne, INT, char, bool, ());
// Special versions for strings - at least avoid copying the first string // Special versions for strings - at least avoid copying the first string
// use super::utils::reg_test; lib.set_fn_2_mut("<", |x: &mut String, y: String| Ok(*x < y));
// reg_test(lib, "<", |x: &mut String, y: String| *x < y, |v| v, map); lib.set_fn_2_mut("<=", |x: &mut String, y: String| Ok(*x <= y));
reg_binary_mut(lib, "<", |x: &mut String, y: String| *x < y, map); lib.set_fn_2_mut(">", |x: &mut String, y: String| Ok(*x > y));
reg_binary_mut(lib, "<=", |x: &mut String, y: String| *x <= y, map); lib.set_fn_2_mut(">=", |x: &mut String, y: String| Ok(*x >= y));
reg_binary_mut(lib, ">", |x: &mut String, y: String| *x > y, map); lib.set_fn_2_mut("==", |x: &mut String, y: String| Ok(*x == y));
reg_binary_mut(lib, ">=", |x: &mut String, y: String| *x >= y, map); lib.set_fn_2_mut("!=", |x: &mut String, y: String| Ok(*x != y));
reg_binary_mut(lib, "==", |x: &mut String, y: String| *x == y, map);
reg_binary_mut(lib, "!=", |x: &mut String, y: String| *x != y, map);
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
@ -85,7 +83,7 @@ def_package!(crate:LogicPackage:"Logical operators.", lib, {
//reg_op!(lib, "||", or, bool); //reg_op!(lib, "||", or, bool);
//reg_op!(lib, "&&", and, bool); //reg_op!(lib, "&&", and, bool);
reg_binary(lib, "|", or, map); lib.set_fn_2("|", or);
reg_binary(lib, "&", and, map); lib.set_fn_2("&", and);
reg_unary(lib, "!", not, map); lib.set_fn_1("!", not);
}); });

View File

@ -1,11 +1,9 @@
#![cfg(not(feature = "no_object"))] #![cfg(not(feature = "no_object"))]
use super::{reg_binary, reg_binary_mut, reg_unary_mut};
use crate::any::Dynamic; use crate::any::Dynamic;
use crate::def_package; use crate::def_package;
use crate::engine::Map; use crate::engine::Map;
use crate::fn_register::map_dynamic as map; use crate::module::FuncReturn;
use crate::parser::INT; use crate::parser::INT;
use crate::stdlib::{ use crate::stdlib::{
@ -13,55 +11,51 @@ use crate::stdlib::{
vec::Vec, vec::Vec,
}; };
fn map_get_keys(map: &mut Map) -> Vec<Dynamic> { fn map_get_keys(map: &mut Map) -> FuncReturn<Vec<Dynamic>> {
map.iter().map(|(k, _)| k.to_string().into()).collect() Ok(map.iter().map(|(k, _)| k.to_string().into()).collect())
} }
fn map_get_values(map: &mut Map) -> Vec<Dynamic> { fn map_get_values(map: &mut Map) -> FuncReturn<Vec<Dynamic>> {
map.iter().map(|(_, v)| v.clone()).collect() Ok(map.iter().map(|(_, v)| v.clone()).collect())
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
reg_binary_mut( lib.set_fn_2_mut(
lib,
"has", "has",
|map: &mut Map, prop: String| map.contains_key(&prop), |map: &mut Map, prop: String| Ok(map.contains_key(&prop)),
map,
); );
reg_unary_mut(lib, "len", |map: &mut Map| map.len() as INT, map); lib.set_fn_1_mut("len", |map: &mut Map| Ok(map.len() as INT));
reg_unary_mut(lib, "clear", |map: &mut Map| map.clear(), map); lib.set_fn_1_mut("clear", |map: &mut Map| {
reg_binary_mut( map.clear();
lib, Ok(())
});
lib.set_fn_2_mut(
"remove", "remove",
|x: &mut Map, name: String| x.remove(&name).unwrap_or_else(|| ().into()), |x: &mut Map, name: String| Ok(x.remove(&name).unwrap_or_else(|| ().into())),
map,
); );
reg_binary_mut( lib.set_fn_2_mut(
lib,
"mixin", "mixin",
|map1: &mut Map, map2: Map| { |map1: &mut Map, map2: Map| {
map2.into_iter().for_each(|(key, value)| { map2.into_iter().for_each(|(key, value)| {
map1.insert(key, value); map1.insert(key, value);
}); });
Ok(())
}, },
map,
); );
reg_binary( lib.set_fn_2(
lib,
"+", "+",
|mut map1: Map, map2: Map| { |mut map1: Map, map2: Map| {
map2.into_iter().for_each(|(key, value)| { map2.into_iter().for_each(|(key, value)| {
map1.insert(key, value); map1.insert(key, value);
}); });
map1 Ok(map1)
}, },
map,
); );
// Register map access functions // Register map access functions
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
reg_unary_mut(lib, "keys", map_get_keys, map); lib.set_fn_1_mut("keys", map_get_keys);
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
reg_unary_mut(lib, "values", map_get_values, map); lib.set_fn_1_mut("values", map_get_values);
}); });

View File

@ -1,7 +1,4 @@
use super::{reg_binary, reg_unary};
use crate::def_package; use crate::def_package;
use crate::fn_register::{map_dynamic as map, map_result as result};
use crate::parser::INT; use crate::parser::INT;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::token::Position; use crate::token::Position;
@ -20,78 +17,77 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
{ {
// Advanced math functions // Advanced math functions
reg_unary(lib, "sin", |x: FLOAT| x.to_radians().sin(), map); lib.set_fn_1("sin", |x: FLOAT| Ok(x.to_radians().sin()));
reg_unary(lib, "cos", |x: FLOAT| x.to_radians().cos(), map); lib.set_fn_1("cos", |x: FLOAT| Ok(x.to_radians().cos()));
reg_unary(lib, "tan", |x: FLOAT| x.to_radians().tan(), map); lib.set_fn_1("tan", |x: FLOAT| Ok(x.to_radians().tan()));
reg_unary(lib, "sinh", |x: FLOAT| x.to_radians().sinh(), map); lib.set_fn_1("sinh", |x: FLOAT| Ok(x.to_radians().sinh()));
reg_unary(lib, "cosh", |x: FLOAT| x.to_radians().cosh(), map); lib.set_fn_1("cosh", |x: FLOAT| Ok(x.to_radians().cosh()));
reg_unary(lib, "tanh", |x: FLOAT| x.to_radians().tanh(), map); lib.set_fn_1("tanh", |x: FLOAT| Ok(x.to_radians().tanh()));
reg_unary(lib, "asin", |x: FLOAT| x.asin().to_degrees(), map); lib.set_fn_1("asin", |x: FLOAT| Ok(x.asin().to_degrees()));
reg_unary(lib, "acos", |x: FLOAT| x.acos().to_degrees(), map); lib.set_fn_1("acos", |x: FLOAT| Ok(x.acos().to_degrees()));
reg_unary(lib, "atan", |x: FLOAT| x.atan().to_degrees(), map); lib.set_fn_1("atan", |x: FLOAT| Ok(x.atan().to_degrees()));
reg_unary(lib, "asinh", |x: FLOAT| x.asinh().to_degrees(), map); lib.set_fn_1("asinh", |x: FLOAT| Ok(x.asinh().to_degrees()));
reg_unary(lib, "acosh", |x: FLOAT| x.acosh().to_degrees(), map); lib.set_fn_1("acosh", |x: FLOAT| Ok(x.acosh().to_degrees()));
reg_unary(lib, "atanh", |x: FLOAT| x.atanh().to_degrees(), map); lib.set_fn_1("atanh", |x: FLOAT| Ok(x.atanh().to_degrees()));
reg_unary(lib, "sqrt", |x: FLOAT| x.sqrt(), map); lib.set_fn_1("sqrt", |x: FLOAT| Ok(x.sqrt()));
reg_unary(lib, "exp", |x: FLOAT| x.exp(), map); lib.set_fn_1("exp", |x: FLOAT| Ok(x.exp()));
reg_unary(lib, "ln", |x: FLOAT| x.ln(), map); lib.set_fn_1("ln", |x: FLOAT| Ok(x.ln()));
reg_binary(lib, "log", |x: FLOAT, base: FLOAT| x.log(base), map); lib.set_fn_2("log", |x: FLOAT, base: FLOAT| Ok(x.log(base)));
reg_unary(lib, "log10", |x: FLOAT| x.log10(), map); lib.set_fn_1("log10", |x: FLOAT| Ok(x.log10()));
reg_unary(lib, "floor", |x: FLOAT| x.floor(), map); lib.set_fn_1("floor", |x: FLOAT| Ok(x.floor()));
reg_unary(lib, "ceiling", |x: FLOAT| x.ceil(), map); lib.set_fn_1("ceiling", |x: FLOAT| Ok(x.ceil()));
reg_unary(lib, "round", |x: FLOAT| x.ceil(), map); lib.set_fn_1("round", |x: FLOAT| Ok(x.ceil()));
reg_unary(lib, "int", |x: FLOAT| x.trunc(), map); lib.set_fn_1("int", |x: FLOAT| Ok(x.trunc()));
reg_unary(lib, "fraction", |x: FLOAT| x.fract(), map); lib.set_fn_1("fraction", |x: FLOAT| Ok(x.fract()));
reg_unary(lib, "is_nan", |x: FLOAT| x.is_nan(), map); lib.set_fn_1("is_nan", |x: FLOAT| Ok(x.is_nan()));
reg_unary(lib, "is_finite", |x: FLOAT| x.is_finite(), map); lib.set_fn_1("is_finite", |x: FLOAT| Ok(x.is_finite()));
reg_unary(lib, "is_infinite", |x: FLOAT| x.is_infinite(), map); lib.set_fn_1("is_infinite", |x: FLOAT| Ok(x.is_infinite()));
// Register conversion functions // Register conversion functions
reg_unary(lib, "to_float", |x: INT| x as FLOAT, map); lib.set_fn_1("to_float", |x: INT| Ok(x as FLOAT));
reg_unary(lib, "to_float", |x: f32| x as FLOAT, map); lib.set_fn_1("to_float", |x: f32| Ok(x as FLOAT));
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
reg_unary(lib, "to_float", |x: i8| x as FLOAT, map); lib.set_fn_1("to_float", |x: i8| Ok(x as FLOAT));
reg_unary(lib, "to_float", |x: u8| x as FLOAT, map); lib.set_fn_1("to_float", |x: u8| Ok(x as FLOAT));
reg_unary(lib, "to_float", |x: i16| x as FLOAT, map); lib.set_fn_1("to_float", |x: i16| Ok(x as FLOAT));
reg_unary(lib, "to_float", |x: u16| x as FLOAT, map); lib.set_fn_1("to_float", |x: u16| Ok(x as FLOAT));
reg_unary(lib, "to_float", |x: i32| x as FLOAT, map); lib.set_fn_1("to_float", |x: i32| Ok(x as FLOAT));
reg_unary(lib, "to_float", |x: u32| x as FLOAT, map); lib.set_fn_1("to_float", |x: u32| Ok(x as FLOAT));
reg_unary(lib, "to_float", |x: i64| x as FLOAT, map); lib.set_fn_1("to_float", |x: i64| Ok(x as FLOAT));
reg_unary(lib, "to_float", |x: u64| x as FLOAT, map); lib.set_fn_1("to_float", |x: u64| Ok(x as FLOAT));
reg_unary(lib, "to_float", |x: i128| x as FLOAT, map); lib.set_fn_1("to_float", |x: i128| Ok(x as FLOAT));
reg_unary(lib, "to_float", |x: u128| x as FLOAT, map); lib.set_fn_1("to_float", |x: u128| Ok(x as FLOAT));
} }
} }
reg_unary(lib, "to_int", |ch: char| ch as INT, map); lib.set_fn_1("to_int", |ch: char| Ok(ch as INT));
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
reg_unary(lib, "to_int", |x: i8| x as INT, map); lib.set_fn_1("to_int", |x: i8| Ok(x as INT));
reg_unary(lib, "to_int", |x: u8| x as INT, map); lib.set_fn_1("to_int", |x: u8| Ok(x as INT));
reg_unary(lib, "to_int", |x: i16| x as INT, map); lib.set_fn_1("to_int", |x: i16| Ok(x as INT));
reg_unary(lib, "to_int", |x: u16| x as INT, map); lib.set_fn_1("to_int", |x: u16| Ok(x as INT));
} }
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
{ {
reg_unary(lib, "to_int", |x: i32| x as INT, map); lib.set_fn_1("to_int", |x: i32| Ok(x as INT));
reg_unary(lib, "to_int", |x: u64| x as INT, map); lib.set_fn_1("to_int", |x: u64| Ok(x as INT));
#[cfg(feature = "only_i64")] #[cfg(feature = "only_i64")]
reg_unary(lib, "to_int", |x: u32| x as INT, map); lib.set_fn_1("to_int", |x: u32| Ok(x as INT));
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
{ {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
{ {
reg_unary( lib.set_fn_1(
lib,
"to_int", "to_int",
|x: f32| { |x: f32| {
if x > (MAX_INT as f32) { if x > (MAX_INT as f32) {
@ -103,10 +99,8 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
Ok(x.trunc() as INT) Ok(x.trunc() as INT)
}, },
result,
); );
reg_unary( lib.set_fn_1(
lib,
"to_int", "to_int",
|x: FLOAT| { |x: FLOAT| {
if x > (MAX_INT as FLOAT) { if x > (MAX_INT as FLOAT) {
@ -118,14 +112,13 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
Ok(x.trunc() as INT) Ok(x.trunc() as INT)
}, },
result,
); );
} }
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]
{ {
reg_unary(lib, "to_int", |x: f32| x as INT, map); lib.set_fn_1("to_int", |x: f32| Ok(x as INT));
reg_unary(lib, "to_int", |x: f64| x as INT, map); lib.set_fn_1("to_int", |x: f64| Ok(x as INT));
} }
} }
}); });

View File

@ -1,8 +1,9 @@
//! This module contains all built-in _packages_ available to Rhai, plus facilities to define custom packages. //! This module contains all built-in _packages_ available to Rhai, plus facilities to define custom packages.
use crate::engine::{FnAny, IteratorFn}; use crate::fn_native::{NativeCallable, SharedIteratorFunction};
use crate::module::Module;
use crate::stdlib::{any::TypeId, boxed::Box, collections::HashMap, rc::Rc, sync::Arc}; use crate::stdlib::{any::TypeId, boxed::Box, collections::HashMap, rc::Rc, sync::Arc, vec::Vec};
mod arithmetic; mod arithmetic;
mod array_basic; mod array_basic;
@ -15,7 +16,6 @@ mod pkg_std;
mod string_basic; mod string_basic;
mod string_more; mod string_more;
mod time_basic; mod time_basic;
mod utils;
pub use arithmetic::ArithmeticPackage; pub use arithmetic::ArithmeticPackage;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -32,41 +32,107 @@ pub use string_more::MoreStringPackage;
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
pub use time_basic::BasicTimePackage; pub use time_basic::BasicTimePackage;
pub use utils::*;
/// Trait that all packages must implement. /// Trait that all packages must implement.
pub trait Package { pub trait Package {
/// Create a new instance of a package.
fn new() -> Self;
/// Register all the functions in a package into a store. /// Register all the functions in a package into a store.
fn init(lib: &mut PackageStore); fn init(lib: &mut Module);
/// Retrieve the generic package library from this package. /// Retrieve the generic package library from this package.
fn get(&self) -> PackageLibrary; fn get(&self) -> PackageLibrary;
} }
/// Type to store all functions in the package. /// Type which `Rc`-wraps a `Module` to facilitate sharing library instances.
#[derive(Default)] #[cfg(not(feature = "sync"))]
pub struct PackageStore { pub type PackageLibrary = Rc<Module>;
/// All functions, keyed by a hash created from the function name and parameter types.
pub functions: HashMap<u64, Box<FnAny>>,
/// All iterator functions, keyed by the type producing the iterator. /// Type which `Arc`-wraps a `Module` to facilitate sharing library instances.
pub type_iterators: HashMap<TypeId, Box<IteratorFn>>, #[cfg(feature = "sync")]
pub type PackageLibrary = Arc<Module>;
/// Type containing a collection of `PackageLibrary` instances.
/// All function and type iterator keys in the loaded packages are indexed for fast access.
#[derive(Clone, Default)]
pub(crate) struct PackagesCollection {
/// Collection of `PackageLibrary` instances.
packages: Vec<PackageLibrary>,
} }
impl PackageStore { impl PackagesCollection {
/// Create a new `PackageStore`. /// Add a `PackageLibrary` into the `PackagesCollection`.
pub fn new() -> Self { pub fn push(&mut self, package: PackageLibrary) {
Default::default() // Later packages override previous ones.
self.packages.insert(0, package);
}
/// Does the specified function hash key exist in the `PackagesCollection`?
pub fn contains_fn(&self, hash: u64) -> bool {
self.packages.iter().any(|p| p.contains_fn(hash))
}
/// Get specified function via its hash key.
pub fn get_fn(&self, hash: u64) -> Option<&Box<dyn NativeCallable>> {
self.packages
.iter()
.map(|p| p.get_fn(hash))
.find(|f| f.is_some())
.flatten()
}
/// Does the specified TypeId iterator exist in the `PackagesCollection`?
pub fn contains_iter(&self, id: TypeId) -> bool {
self.packages.iter().any(|p| p.contains_iter(id))
}
/// Get the specified TypeId iterator.
pub fn get_iter(&self, id: TypeId) -> Option<&SharedIteratorFunction> {
self.packages
.iter()
.map(|p| p.get_iter(id))
.find(|f| f.is_some())
.flatten()
} }
} }
/// Type which `Rc`-wraps a `PackageStore` to facilitate sharing library instances. /// This macro makes it easy to define a _package_ (which is basically a shared module)
#[cfg(not(feature = "sync"))] /// and register functions into it.
pub type PackageLibrary = Rc<PackageStore>; ///
/// Functions can be added to the package using the standard module methods such as
/// `set_fn_2`, `set_fn_3_mut`, `set_fn_0` etc.
///
/// # Examples
///
/// ```
/// use rhai::{Dynamic, EvalAltResult};
/// use rhai::def_package;
///
/// fn add(x: i64, y: i64) -> Result<i64, Box<EvalAltResult>> { Ok(x + y) }
///
/// def_package!(rhai:MyPackage:"My super-duper package", lib,
/// {
/// // Load a binary function with all value parameters.
/// lib.set_fn_2("my_add", add);
/// });
/// ```
///
/// The above defines a package named 'MyPackage' with a single function named 'my_add'.
#[macro_export]
macro_rules! def_package {
($root:ident : $package:ident : $comment:expr , $lib:ident , $block:stmt) => {
#[doc=$comment]
pub struct $package($root::packages::PackageLibrary);
/// Type which `Arc`-wraps a `PackageStore` to facilitate sharing library instances. impl $root::packages::Package for $package {
#[cfg(feature = "sync")] fn get(&self) -> $root::packages::PackageLibrary {
pub type PackageLibrary = Arc<PackageStore>; self.0.clone()
}
fn init($lib: &mut $root::Module) {
$block
}
}
impl $package {
pub fn new() -> Self {
let mut module = $root::Module::new_with_capacity(512);
<Self as $root::packages::Package>::init(&mut module);
Self(module.into())
}
}
};
}

View File

@ -1,8 +1,6 @@
use super::{reg_binary, reg_binary_mut, reg_none, reg_unary, reg_unary_mut};
use crate::def_package; use crate::def_package;
use crate::engine::{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::module::FuncReturn;
use crate::parser::INT; use crate::parser::INT;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -18,31 +16,33 @@ use crate::stdlib::{
}; };
// Register print and debug // Register print and debug
fn to_debug<T: Debug>(x: &mut T) -> String { fn to_debug<T: Debug>(x: &mut T) -> FuncReturn<String> {
format!("{:?}", x) Ok(format!("{:?}", x))
} }
fn to_string<T: Display>(x: &mut T) -> String { fn to_string<T: Display>(x: &mut T) -> FuncReturn<String> {
format!("{}", x) Ok(format!("{}", x))
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
fn format_map(x: &mut Map) -> String { fn format_map(x: &mut Map) -> FuncReturn<String> {
format!("#{:?}", x) Ok(format!("#{:?}", x))
} }
macro_rules! reg_op { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { macro_rules! reg_op {
$(reg_unary_mut($lib, $op, $func::<$par>, map);)* }; ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
$( $lib.set_fn_1_mut($op, $func::<$par>); )*
};
} }
def_package!(crate:BasicStringPackage:"Basic string utilities, including printing.", lib, { def_package!(crate:BasicStringPackage:"Basic string utilities, including printing.", lib, {
reg_op!(lib, KEYWORD_PRINT, to_string, INT, bool, char); reg_op!(lib, KEYWORD_PRINT, to_string, INT, bool, char);
reg_op!(lib, FUNC_TO_STRING, to_string, INT, bool, char); reg_op!(lib, FUNC_TO_STRING, to_string, INT, bool, char);
reg_none(lib, KEYWORD_PRINT, || "".to_string(), map); lib.set_fn_0(KEYWORD_PRINT, || Ok("".to_string()));
reg_unary(lib, KEYWORD_PRINT, |_: ()| "".to_string(), map); lib.set_fn_1(KEYWORD_PRINT, |_: ()| Ok("".to_string()));
reg_unary(lib, FUNC_TO_STRING, |_: ()| "".to_string(), map); lib.set_fn_1(FUNC_TO_STRING, |_: ()| Ok("".to_string()));
reg_unary_mut(lib, KEYWORD_PRINT, |s: &mut String| s.clone(), map); lib.set_fn_1_mut(KEYWORD_PRINT, |s: &mut String| Ok(s.clone()));
reg_unary_mut(lib, FUNC_TO_STRING, |s: &mut String| s.clone(), map); lib.set_fn_1_mut(FUNC_TO_STRING, |s: &mut String| Ok(s.clone()));
reg_op!(lib, KEYWORD_DEBUG, to_debug, INT, bool, (), char, String); reg_op!(lib, KEYWORD_DEBUG, to_debug, INT, bool, (), char, String);
@ -73,34 +73,34 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
{ {
reg_unary_mut(lib, KEYWORD_PRINT, format_map, map); lib.set_fn_1_mut(KEYWORD_PRINT, format_map);
reg_unary_mut(lib, FUNC_TO_STRING, format_map, map); lib.set_fn_1_mut(FUNC_TO_STRING, format_map);
reg_unary_mut(lib, KEYWORD_DEBUG, format_map, map); lib.set_fn_1_mut(KEYWORD_DEBUG, format_map);
} }
reg_binary( lib.set_fn_2(
lib,
"+", "+",
|mut s: String, ch: char| { |mut s: String, ch: char| {
s.push(ch); s.push(ch);
s Ok(s)
}, },
map,
); );
reg_binary( lib.set_fn_2(
lib,
"+", "+",
|mut s: String, s2: String| { |mut s: String, s2: String| {
s.push_str(&s2); s.push_str(&s2);
s Ok(s)
}, },
map,
); );
reg_binary_mut(lib, "append", |s: &mut String, ch: char| s.push(ch), map); lib.set_fn_2_mut("append", |s: &mut String, ch: char| {
reg_binary_mut( s.push(ch);
lib, Ok(())
});
lib.set_fn_2_mut(
"append", "append",
|s: &mut String, s2: String| s.push_str(&s2), |s: &mut String, s2: String| {
map, s.push_str(&s2);
Ok(())
}
); );
}); });

View File

@ -1,7 +1,5 @@
use super::{reg_binary, reg_binary_mut, reg_trinary_mut, reg_unary_mut};
use crate::def_package; use crate::def_package;
use crate::fn_register::map_dynamic as map; use crate::module::FuncReturn;
use crate::parser::INT; use crate::parser::INT;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -14,19 +12,19 @@ use crate::stdlib::{
vec::Vec, vec::Vec,
}; };
fn prepend<T: Display>(x: T, y: String) -> String { fn prepend<T: Display>(x: T, y: String) -> FuncReturn<String> {
format!("{}{}", x, y) Ok(format!("{}{}", x, y))
} }
fn append<T: Display>(x: String, y: T) -> String { fn append<T: Display>(x: String, y: T) -> FuncReturn<String> {
format!("{}{}", x, y) Ok(format!("{}{}", x, y))
} }
fn sub_string(s: &mut String, start: INT, len: INT) -> String { fn sub_string(s: &mut String, start: INT, len: INT) -> FuncReturn<String> {
let offset = if s.is_empty() || len <= 0 { let offset = if s.is_empty() || len <= 0 {
return "".to_string(); return Ok("".to_string());
} else if start < 0 { } else if start < 0 {
0 0
} else if (start as usize) >= s.chars().count() { } else if (start as usize) >= s.chars().count() {
return "".to_string(); return Ok("".to_string());
} else { } else {
start as usize start as usize
}; };
@ -39,17 +37,17 @@ fn sub_string(s: &mut String, start: INT, len: INT) -> String {
len as usize len as usize
}; };
chars[offset..][..len].into_iter().collect() Ok(chars[offset..][..len].into_iter().collect())
} }
fn crop_string(s: &mut String, start: INT, len: INT) { fn crop_string(s: &mut String, start: INT, len: INT) -> FuncReturn<()> {
let offset = if s.is_empty() || len <= 0 { let offset = if s.is_empty() || len <= 0 {
s.clear(); s.clear();
return; return Ok(());
} else if start < 0 { } else if start < 0 {
0 0
} else if (start as usize) >= s.chars().count() { } else if (start as usize) >= s.chars().count() {
s.clear(); s.clear();
return; return Ok(());
} else { } else {
start as usize start as usize
}; };
@ -67,18 +65,22 @@ fn crop_string(s: &mut String, start: INT, len: INT) {
chars[offset..][..len] chars[offset..][..len]
.into_iter() .into_iter()
.for_each(|&ch| s.push(ch)); .for_each(|&ch| s.push(ch));
Ok(())
} }
macro_rules! reg_op { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { macro_rules! reg_op {
$(reg_binary($lib, $op, $func::<$par>, map);)* }; ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
$( $lib.set_fn_2($op, $func::<$par>); )*
};
} }
def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, { def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, {
reg_op!(lib, "+", append, INT, bool, char); reg_op!(lib, "+", append, INT, bool, char);
reg_binary_mut(lib, "+", |x: &mut String, _: ()| x.clone(), map); lib.set_fn_2_mut( "+", |x: &mut String, _: ()| Ok(x.clone()));
reg_op!(lib, "+", prepend, INT, bool, char); reg_op!(lib, "+", prepend, INT, bool, char);
reg_binary(lib, "+", |_: (), y: String| y, map); lib.set_fn_2("+", |_: (), y: String| Ok(y));
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
@ -95,105 +97,95 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
{ {
reg_binary(lib, "+", |x: String, y: Array| format!("{}{:?}", x, y), map); lib.set_fn_2("+", |x: String, y: Array| Ok(format!("{}{:?}", x, y)));
reg_binary(lib, "+", |x: Array, y: String| format!("{:?}{}", x, y), map); lib.set_fn_2("+", |x: Array, y: String| Ok(format!("{:?}{}", x, y)));
} }
reg_unary_mut(lib, "len", |s: &mut String| s.chars().count() as INT, map); lib.set_fn_1_mut("len", |s: &mut String| Ok(s.chars().count() as INT));
reg_binary_mut( lib.set_fn_2_mut(
lib,
"contains", "contains",
|s: &mut String, ch: char| s.contains(ch), |s: &mut String, ch: char| Ok(s.contains(ch)),
map,
); );
reg_binary_mut( lib.set_fn_2_mut(
lib,
"contains", "contains",
|s: &mut String, find: String| s.contains(&find), |s: &mut String, find: String| Ok(s.contains(&find)),
map,
); );
reg_trinary_mut( lib.set_fn_3_mut(
lib,
"index_of", "index_of",
|s: &mut String, ch: char, start: INT| { |s: &mut String, ch: char, start: INT| {
let start = if start < 0 { let start = if start < 0 {
0 0
} else if (start as usize) >= s.chars().count() { } else if (start as usize) >= s.chars().count() {
return -1 as INT; return Ok(-1 as INT);
} else { } else {
s.chars().take(start as usize).collect::<String>().len() s.chars().take(start as usize).collect::<String>().len()
}; };
s[start..] Ok(s[start..]
.find(ch) .find(ch)
.map(|index| s[0..start + index].chars().count() as INT) .map(|index| s[0..start + index].chars().count() as INT)
.unwrap_or(-1 as INT) .unwrap_or(-1 as INT))
}, },
map,
); );
reg_binary_mut( lib.set_fn_2_mut(
lib,
"index_of", "index_of",
|s: &mut String, ch: char| { |s: &mut String, ch: char| {
s.find(ch) Ok(s.find(ch)
.map(|index| s[0..index].chars().count() as INT) .map(|index| s[0..index].chars().count() as INT)
.unwrap_or(-1 as INT) .unwrap_or(-1 as INT))
}, },
map,
); );
reg_trinary_mut( lib.set_fn_3_mut(
lib,
"index_of", "index_of",
|s: &mut String, find: String, start: INT| { |s: &mut String, find: String, start: INT| {
let start = if start < 0 { let start = if start < 0 {
0 0
} else if (start as usize) >= s.chars().count() { } else if (start as usize) >= s.chars().count() {
return -1 as INT; return Ok(-1 as INT);
} else { } else {
s.chars().take(start as usize).collect::<String>().len() s.chars().take(start as usize).collect::<String>().len()
}; };
s[start..] Ok(s[start..]
.find(&find) .find(&find)
.map(|index| s[0..start + index].chars().count() as INT) .map(|index| s[0..start + index].chars().count() as INT)
.unwrap_or(-1 as INT) .unwrap_or(-1 as INT))
}, },
map,
); );
reg_binary_mut( lib.set_fn_2_mut(
lib,
"index_of", "index_of",
|s: &mut String, find: String| { |s: &mut String, find: String| {
s.find(&find) Ok(s.find(&find)
.map(|index| s[0..index].chars().count() as INT) .map(|index| s[0..index].chars().count() as INT)
.unwrap_or(-1 as INT) .unwrap_or(-1 as INT))
}, },
map,
); );
reg_unary_mut(lib, "clear", |s: &mut String| s.clear(), map); lib.set_fn_1_mut("clear", |s: &mut String| {
reg_binary_mut(lib, "append", |s: &mut String, ch: char| s.push(ch), map); s.clear();
reg_binary_mut( Ok(())
lib, });
lib.set_fn_2_mut( "append", |s: &mut String, ch: char| {
s.push(ch);
Ok(())
});
lib.set_fn_2_mut(
"append", "append",
|s: &mut String, add: String| s.push_str(&add), |s: &mut String, add: String| {
map, s.push_str(&add);
Ok(())
}
); );
reg_trinary_mut(lib, "sub_string", sub_string, map); lib.set_fn_3_mut( "sub_string", sub_string);
reg_binary_mut( lib.set_fn_2_mut(
lib,
"sub_string", "sub_string",
|s: &mut String, start: INT| sub_string(s, start, s.len() as INT), |s: &mut String, start: INT| sub_string(s, start, s.len() as INT),
map,
); );
reg_trinary_mut(lib, "crop", crop_string, map); lib.set_fn_3_mut( "crop", crop_string);
reg_binary_mut( lib.set_fn_2_mut(
lib,
"crop", "crop",
|s: &mut String, start: INT| crop_string(s, start, s.len() as INT), |s: &mut String, start: INT| crop_string(s, start, s.len() as INT),
map,
); );
reg_binary_mut( lib.set_fn_2_mut(
lib,
"truncate", "truncate",
|s: &mut String, len: INT| { |s: &mut String, len: INT| {
if len >= 0 { if len >= 0 {
@ -203,31 +195,55 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
} else { } else {
s.clear(); s.clear();
} }
Ok(())
}, },
map,
); );
reg_trinary_mut( lib.set_fn_3_mut(
lib,
"pad", "pad",
|s: &mut String, len: INT, ch: char| { |s: &mut String, len: INT, ch: char| {
for _ in 0..s.chars().count() - len as usize { for _ in 0..s.chars().count() - len as usize {
s.push(ch); s.push(ch);
} }
Ok(())
}, },
map,
); );
reg_trinary_mut( lib.set_fn_3_mut(
lib,
"replace", "replace",
|s: &mut String, find: String, sub: String| { |s: &mut String, find: String, sub: String| {
let new_str = s.replace(&find, &sub); let new_str = s.replace(&find, &sub);
s.clear(); s.clear();
s.push_str(&new_str); s.push_str(&new_str);
Ok(())
}, },
map,
); );
reg_unary_mut( lib.set_fn_3_mut(
lib, "replace",
|s: &mut String, find: String, sub: char| {
let new_str = s.replace(&find, &sub.to_string());
s.clear();
s.push_str(&new_str);
Ok(())
},
);
lib.set_fn_3_mut(
"replace",
|s: &mut String, find: char, sub: String| {
let new_str = s.replace(&find.to_string(), &sub);
s.clear();
s.push_str(&new_str);
Ok(())
},
);
lib.set_fn_3_mut(
"replace",
|s: &mut String, find: char, sub: char| {
let new_str = s.replace(&find.to_string(), &sub.to_string());
s.clear();
s.push_str(&new_str);
Ok(())
},
);
lib.set_fn_1_mut(
"trim", "trim",
|s: &mut String| { |s: &mut String| {
let trimmed = s.trim(); let trimmed = s.trim();
@ -235,7 +251,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
if trimmed.len() < s.len() { if trimmed.len() < s.len() {
*s = trimmed.to_string(); *s = trimmed.to_string();
} }
Ok(())
}, },
map,
); );
}); });

View File

@ -1,9 +1,8 @@
use super::logic::{eq, gt, gte, lt, lte, ne}; use super::logic::{eq, gt, gte, lt, lte, ne};
use super::math_basic::MAX_INT; use super::math_basic::MAX_INT;
use super::{reg_binary, reg_none, reg_unary};
use crate::def_package; use crate::def_package;
use crate::fn_register::{map_dynamic as map, map_result as result}; use crate::module::FuncReturn;
use crate::parser::INT; use crate::parser::INT;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::token::Position; use crate::token::Position;
@ -14,10 +13,9 @@ use crate::stdlib::time::Instant;
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, { def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
// Register date/time functions // Register date/time functions
reg_none(lib, "timestamp", || Instant::now(), map); lib.set_fn_0("timestamp", || Ok(Instant::now()));
reg_binary( lib.set_fn_2(
lib,
"-", "-",
|ts1: Instant, ts2: Instant| { |ts1: Instant, ts2: Instant| {
if ts2 > ts1 { if ts2 > ts1 {
@ -63,18 +61,16 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
} }
} }
}, },
result,
); );
reg_binary(lib, "<", lt::<Instant>, map); lib.set_fn_2("<", lt::<Instant>);
reg_binary(lib, "<=", lte::<Instant>, map); lib.set_fn_2("<=", lte::<Instant>);
reg_binary(lib, ">", gt::<Instant>, map); lib.set_fn_2(">", gt::<Instant>);
reg_binary(lib, ">=", gte::<Instant>, map); lib.set_fn_2(">=", gte::<Instant>);
reg_binary(lib, "==", eq::<Instant>, map); lib.set_fn_2("==", eq::<Instant>);
reg_binary(lib, "!=", ne::<Instant>, map); lib.set_fn_2("!=", ne::<Instant>);
reg_unary( lib.set_fn_1(
lib,
"elapsed", "elapsed",
|timestamp: Instant| { |timestamp: Instant| {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -96,6 +92,5 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
return Ok(seconds as INT); return Ok(seconds as INT);
} }
}, },
result,
); );
}); });

View File

@ -1,444 +0,0 @@
use super::PackageStore;
use crate::any::{Dynamic, Variant};
use crate::calc_fn_hash;
use crate::engine::FnCallArgs;
use crate::result::EvalAltResult;
use crate::token::Position;
use crate::stdlib::{
any::TypeId,
boxed::Box,
mem,
string::{String, ToString},
};
/// This macro makes it easy to define a _package_ and register functions into it.
///
/// Functions can be added to the package using a number of helper functions under the `packages` module,
/// such as `reg_unary`, `reg_binary_mut`, `reg_trinary_mut` etc.
///
/// # Examples
///
/// ```
/// use rhai::Dynamic;
/// use rhai::def_package;
/// use rhai::packages::reg_binary;
///
/// fn add(x: i64, y: i64) -> i64 { x + y }
///
/// def_package!(rhai:MyPackage:"My super-duper package", lib,
/// {
/// reg_binary(lib, "my_add", add, |v, _| Ok(v.into()));
/// // ^^^^^^^^^^^^^^^^^^^
/// // map into Result<Dynamic, Box<EvalAltResult>>
/// });
/// ```
///
/// The above defines a package named 'MyPackage' with a single function named 'my_add'.
#[macro_export]
macro_rules! def_package {
($root:ident : $package:ident : $comment:expr , $lib:ident , $block:stmt) => {
#[doc=$comment]
pub struct $package($root::packages::PackageLibrary);
impl $root::packages::Package for $package {
fn new() -> Self {
let mut pkg = $root::packages::PackageStore::new();
Self::init(&mut pkg);
Self(pkg.into())
}
fn get(&self) -> $root::packages::PackageLibrary {
self.0.clone()
}
fn init($lib: &mut $root::packages::PackageStore) {
$block
}
}
};
}
/// Check whether the correct number of arguments is passed to the function.
fn check_num_args(
name: &str,
num_args: usize,
args: &mut FnCallArgs,
pos: Position,
) -> Result<(), Box<EvalAltResult>> {
if args.len() != num_args {
Err(Box::new(EvalAltResult::ErrorFunctionArgsMismatch(
name.to_string(),
num_args,
args.len(),
pos,
)))
} else {
Ok(())
}
}
/// Add a function with no parameters to the package.
///
/// `map_result` is a function that maps the return type of the function to `Result<Dynamic, EvalAltResult>`.
///
/// # Examples
///
/// ```
/// use rhai::Dynamic;
/// use rhai::def_package;
/// use rhai::packages::reg_none;
///
/// fn get_answer() -> i64 { 42 }
///
/// def_package!(rhai:MyPackage:"My super-duper package", lib,
/// {
/// reg_none(lib, "my_answer", get_answer, |v, _| Ok(v.into()));
/// // ^^^^^^^^^^^^^^^^^^^
/// // map into Result<Dynamic, Box<EvalAltResult>>
/// });
/// ```
///
/// The above defines a package named 'MyPackage' with a single function named 'my_add_1'.
pub fn reg_none<R>(
lib: &mut PackageStore,
fn_name: &'static str,
#[cfg(not(feature = "sync"))] func: impl Fn() -> R + 'static,
#[cfg(feature = "sync")] func: impl Fn() -> R + Send + Sync + 'static,
#[cfg(not(feature = "sync"))] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
+ 'static,
#[cfg(feature = "sync")] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
+ Send
+ Sync
+ 'static,
) {
let hash = calc_fn_hash(fn_name, ([] as [TypeId; 0]).iter().cloned());
let f = Box::new(move |args: &mut FnCallArgs, pos: Position| {
check_num_args(fn_name, 0, args, pos)?;
let r = func();
map_result(r, pos)
});
lib.functions.insert(hash, f);
}
/// Add a function with one parameter to the package.
///
/// `map_result` is a function that maps the return type of the function to `Result<Dynamic, EvalAltResult>`.
///
/// # Examples
///
/// ```
/// use rhai::Dynamic;
/// use rhai::def_package;
/// use rhai::packages::reg_unary;
///
/// fn add_1(x: i64) -> i64 { x + 1 }
///
/// def_package!(rhai:MyPackage:"My super-duper package", lib,
/// {
/// reg_unary(lib, "my_add_1", add_1, |v, _| Ok(v.into()));
/// // ^^^^^^^^^^^^^^^^^^^
/// // map into Result<Dynamic, Box<EvalAltResult>>
/// });
/// ```
///
/// The above defines a package named 'MyPackage' with a single function named 'my_add_1'.
pub fn reg_unary<T: Variant + Clone, R>(
lib: &mut PackageStore,
fn_name: &'static str,
#[cfg(not(feature = "sync"))] func: impl Fn(T) -> R + 'static,
#[cfg(feature = "sync")] func: impl Fn(T) -> R + Send + Sync + 'static,
#[cfg(not(feature = "sync"))] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
+ 'static,
#[cfg(feature = "sync")] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
+ Send
+ Sync
+ 'static,
) {
//println!("register {}({})", fn_name, crate::std::any::type_name::<T>());
let hash = calc_fn_hash(fn_name, [TypeId::of::<T>()].iter().cloned());
let f = Box::new(move |args: &mut FnCallArgs, pos: Position| {
check_num_args(fn_name, 1, args, pos)?;
let mut drain = args.iter_mut();
let x = mem::take(*drain.next().unwrap()).cast::<T>();
let r = func(x);
map_result(r, pos)
});
lib.functions.insert(hash, f);
}
/// Add a function with one mutable reference parameter to the package.
///
/// `map_result` is a function that maps the return type of the function to `Result<Dynamic, EvalAltResult>`.
///
/// # Examples
///
/// ```
/// use rhai::{Dynamic, EvalAltResult};
/// use rhai::def_package;
/// use rhai::packages::reg_unary_mut;
///
/// fn inc(x: &mut i64) -> Result<Dynamic, Box<EvalAltResult>> {
/// if *x == 0 {
/// return Err("boo! zero cannot be incremented!".into())
/// }
/// *x += 1;
/// Ok(().into())
/// }
///
/// def_package!(rhai:MyPackage:"My super-duper package", lib,
/// {
/// reg_unary_mut(lib, "try_inc", inc, |r, _| r);
/// // ^^^^^^^^
/// // map into Result<Dynamic, Box<EvalAltResult>>
/// });
/// ```
///
/// The above defines a package named 'MyPackage' with a single fallible function named 'try_inc'
/// which takes a first argument of `&mut`, return a `Result<Dynamic, Box<EvalAltResult>>`.
pub fn reg_unary_mut<T: Variant + Clone, R>(
lib: &mut PackageStore,
fn_name: &'static str,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut T) -> R + 'static,
#[cfg(feature = "sync")] func: impl Fn(&mut T) -> R + Send + Sync + 'static,
#[cfg(not(feature = "sync"))] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
+ 'static,
#[cfg(feature = "sync")] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
+ Send
+ Sync
+ 'static,
) {
//println!("register {}(&mut {})", fn_name, crate::std::any::type_name::<T>());
let hash = calc_fn_hash(fn_name, [TypeId::of::<T>()].iter().cloned());
let f = Box::new(move |args: &mut FnCallArgs, pos: Position| {
check_num_args(fn_name, 1, args, pos)?;
let mut drain = args.iter_mut();
let x: &mut T = drain.next().unwrap().downcast_mut().unwrap();
let r = func(x);
map_result(r, pos)
});
lib.functions.insert(hash, f);
}
/// Add a function with two parameters to the package.
///
/// `map_result` is a function that maps the return type of the function to `Result<Dynamic, EvalAltResult>`.
///
/// # Examples
///
/// ```
/// use rhai::Dynamic;
/// use rhai::def_package;
/// use rhai::packages::reg_binary;
///
/// fn add(x: i64, y: i64) -> i64 { x + y }
///
/// def_package!(rhai:MyPackage:"My super-duper package", lib,
/// {
/// reg_binary(lib, "my_add", add, |v, _| Ok(v.into()));
/// // ^^^^^^^^^^^^^^^^^^^
/// // map into Result<Dynamic, Box<EvalAltResult>>
/// });
/// ```
///
/// The above defines a package named 'MyPackage' with a single function named 'my_add'.
pub fn reg_binary<A: Variant + Clone, B: Variant + Clone, R>(
lib: &mut PackageStore,
fn_name: &'static str,
#[cfg(not(feature = "sync"))] func: impl Fn(A, B) -> R + 'static,
#[cfg(feature = "sync")] func: impl Fn(A, B) -> R + Send + Sync + 'static,
#[cfg(not(feature = "sync"))] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
+ 'static,
#[cfg(feature = "sync")] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
+ Send
+ Sync
+ 'static,
) {
//println!("register {}({}, {})", fn_name, crate::std::any::type_name::<A>(), crate::std::any::type_name::<B>());
let hash = calc_fn_hash(
fn_name,
[TypeId::of::<A>(), TypeId::of::<B>()].iter().cloned(),
);
let f = Box::new(move |args: &mut FnCallArgs, pos: Position| {
check_num_args(fn_name, 2, args, pos)?;
let mut drain = args.iter_mut();
let x = mem::take(*drain.next().unwrap()).cast::<A>();
let y = mem::take(*drain.next().unwrap()).cast::<B>();
let r = func(x, y);
map_result(r, pos)
});
lib.functions.insert(hash, f);
}
/// Add a function with two parameters (the first one being a mutable reference) to the package.
///
/// `map_result` is a function that maps the return type of the function to `Result<Dynamic, EvalAltResult>`.
///
/// # Examples
///
/// ```
/// use rhai::{Dynamic, EvalAltResult};
/// use rhai::def_package;
/// use rhai::packages::reg_binary_mut;
///
/// fn add(x: &mut i64, y: i64) -> Result<Dynamic, Box<EvalAltResult>> {
/// if y == 0 {
/// return Err("boo! cannot add zero!".into())
/// }
/// *x += y;
/// Ok(().into())
/// }
///
/// def_package!(rhai:MyPackage:"My super-duper package", lib,
/// {
/// reg_binary_mut(lib, "try_add", add, |r, _| r);
/// // ^^^^^^^^
/// // map into Result<Dynamic, Box<EvalAltResult>>
/// });
/// ```
///
/// The above defines a package named 'MyPackage' with a single fallible function named 'try_add'
/// which takes a first argument of `&mut`, return a `Result<Dynamic, Box<EvalAltResult>>`.
pub fn reg_binary_mut<A: Variant + Clone, B: Variant + Clone, R>(
lib: &mut PackageStore,
fn_name: &'static str,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> R + 'static,
#[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> R + Send + Sync + 'static,
#[cfg(not(feature = "sync"))] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
+ 'static,
#[cfg(feature = "sync")] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
+ Send
+ Sync
+ 'static,
) {
//println!("register {}(&mut {}, {})", fn_name, crate::std::any::type_name::<A>(), crate::std::any::type_name::<B>());
let hash = calc_fn_hash(
fn_name,
[TypeId::of::<A>(), TypeId::of::<B>()].iter().cloned(),
);
let f = Box::new(move |args: &mut FnCallArgs, pos: Position| {
check_num_args(fn_name, 2, args, pos)?;
let mut drain = args.iter_mut();
let x: &mut A = drain.next().unwrap().downcast_mut().unwrap();
let y = mem::take(*drain.next().unwrap()).cast::<B>();
let r = func(x, y);
map_result(r, pos)
});
lib.functions.insert(hash, f);
}
/// Add a function with three parameters to the package.
///
/// `map_result` is a function that maps the return type of the function to `Result<Dynamic, EvalAltResult>`.
pub fn reg_trinary<A: Variant + Clone, B: Variant + Clone, C: Variant + Clone, R>(
lib: &mut PackageStore,
fn_name: &'static str,
#[cfg(not(feature = "sync"))] func: impl Fn(A, B, C) -> R + 'static,
#[cfg(feature = "sync")] func: impl Fn(A, B, C) -> R + Send + Sync + 'static,
#[cfg(not(feature = "sync"))] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
+ 'static,
#[cfg(feature = "sync")] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
+ Send
+ Sync
+ 'static,
) {
//println!("register {}({}, {}, {})", fn_name, crate::std::any::type_name::<A>(), crate::std::any::type_name::<B>(), crate::std::any::type_name::<C>());
let hash = calc_fn_hash(
fn_name,
[TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()]
.iter()
.cloned(),
);
let f = Box::new(move |args: &mut FnCallArgs, pos: Position| {
check_num_args(fn_name, 3, args, pos)?;
let mut drain = args.iter_mut();
let x = mem::take(*drain.next().unwrap()).cast::<A>();
let y = mem::take(*drain.next().unwrap()).cast::<B>();
let z = mem::take(*drain.next().unwrap()).cast::<C>();
let r = func(x, y, z);
map_result(r, pos)
});
lib.functions.insert(hash, f);
}
/// Add a function with three parameters (the first one is a mutable reference) to the package.
///
/// `map_result` is a function that maps the return type of the function to `Result<Dynamic, EvalAltResult>`.
pub fn reg_trinary_mut<A: Variant + Clone, B: Variant + Clone, C: Variant + Clone, R>(
lib: &mut PackageStore,
fn_name: &'static str,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B, C) -> R + 'static,
#[cfg(feature = "sync")] func: impl Fn(&mut A, B, C) -> R + Send + Sync + 'static,
#[cfg(not(feature = "sync"))] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
+ 'static,
#[cfg(feature = "sync")] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
+ Send
+ Sync
+ 'static,
) {
//println!("register {}(&mut {}, {}, {})", fn_name, crate::std::any::type_name::<A>(), crate::std::any::type_name::<B>(), crate::std::any::type_name::<C>());
let hash = calc_fn_hash(
fn_name,
[TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()]
.iter()
.cloned(),
);
let f = Box::new(move |args: &mut FnCallArgs, pos: Position| {
check_num_args(fn_name, 3, args, pos)?;
let mut drain = args.iter_mut();
let x: &mut A = drain.next().unwrap().downcast_mut().unwrap();
let y = mem::take(*drain.next().unwrap()).cast::<B>();
let z = mem::take(*drain.next().unwrap()).cast::<C>();
let r = func(x, y, z);
map_result(r, pos)
});
lib.functions.insert(hash, f);
}

File diff suppressed because it is too large Load Diff

View File

@ -33,6 +33,9 @@ pub enum EvalAltResult {
/// Call to an unknown function. Wrapped value is the name of the function. /// Call to an unknown function. Wrapped value is the name of the function.
ErrorFunctionNotFound(String, Position), ErrorFunctionNotFound(String, Position),
/// An error has occurred inside a called function.
/// Wrapped values re the name of the function and the interior error.
ErrorInFunctionCall(String, Box<EvalAltResult>, Position),
/// Function call has incorrect number of arguments. /// Function call has incorrect number of arguments.
/// Wrapped values are the name of the function, the number of parameters required /// Wrapped values are the name of the function, the number of parameters required
/// and the actual number of arguments passed. /// and the actual number of arguments passed.
@ -97,6 +100,7 @@ impl EvalAltResult {
Self::ErrorReadingScriptFile(_, _, _) => "Cannot read from script file", Self::ErrorReadingScriptFile(_, _, _) => "Cannot read from script file",
Self::ErrorParsing(p) => p.desc(), Self::ErrorParsing(p) => p.desc(),
Self::ErrorInFunctionCall(_, _, _) => "Error in called function",
Self::ErrorFunctionNotFound(_, _) => "Function not found", Self::ErrorFunctionNotFound(_, _) => "Function not found",
Self::ErrorFunctionArgsMismatch(_, _, _, _) => { Self::ErrorFunctionArgsMismatch(_, _, _, _) => {
"Function call with wrong number of arguments" "Function call with wrong number of arguments"
@ -160,6 +164,10 @@ impl fmt::Display for EvalAltResult {
Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p), Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p),
Self::ErrorInFunctionCall(s, err, pos) => {
write!(f, "Error in call to function '{}' ({}): {}", s, pos, err)
}
Self::ErrorFunctionNotFound(s, pos) Self::ErrorFunctionNotFound(s, pos)
| Self::ErrorVariableNotFound(s, pos) | Self::ErrorVariableNotFound(s, pos)
| Self::ErrorModuleNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), | Self::ErrorModuleNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos),
@ -262,6 +270,7 @@ impl<T: AsRef<str>> From<T> for Box<EvalAltResult> {
} }
impl EvalAltResult { impl EvalAltResult {
/// Get the `Position` of this error.
pub fn position(&self) -> Position { pub fn position(&self) -> Position {
match self { match self {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
@ -270,6 +279,7 @@ impl EvalAltResult {
Self::ErrorParsing(err) => err.position(), Self::ErrorParsing(err) => err.position(),
Self::ErrorFunctionNotFound(_, pos) Self::ErrorFunctionNotFound(_, pos)
| Self::ErrorInFunctionCall(_, _, pos)
| Self::ErrorFunctionArgsMismatch(_, _, _, pos) | Self::ErrorFunctionArgsMismatch(_, _, _, pos)
| Self::ErrorBooleanArgMismatch(_, pos) | Self::ErrorBooleanArgMismatch(_, pos)
| Self::ErrorCharMismatch(pos) | Self::ErrorCharMismatch(pos)
@ -296,16 +306,16 @@ impl EvalAltResult {
} }
} }
/// Consume the current `EvalAltResult` and return a new one /// Override the `Position` of this error.
/// with the specified `Position`. pub fn set_position(&mut self, new_position: Position) {
pub(crate) fn set_position(mut err: Box<Self>, new_position: Position) -> Box<Self> { match self {
match err.as_mut() {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Self::ErrorReadingScriptFile(_, pos, _) => *pos = new_position, Self::ErrorReadingScriptFile(_, pos, _) => *pos = new_position,
Self::ErrorParsing(err) => err.1 = new_position, Self::ErrorParsing(err) => err.1 = new_position,
Self::ErrorFunctionNotFound(_, pos) Self::ErrorFunctionNotFound(_, pos)
| Self::ErrorInFunctionCall(_, _, pos)
| Self::ErrorFunctionArgsMismatch(_, _, _, pos) | Self::ErrorFunctionArgsMismatch(_, _, _, pos)
| Self::ErrorBooleanArgMismatch(_, pos) | Self::ErrorBooleanArgMismatch(_, pos)
| Self::ErrorCharMismatch(pos) | Self::ErrorCharMismatch(pos)
@ -330,7 +340,12 @@ impl EvalAltResult {
| Self::ErrorLoopBreak(_, pos) | Self::ErrorLoopBreak(_, pos)
| Self::Return(_, pos) => *pos = new_position, | Self::Return(_, pos) => *pos = new_position,
} }
}
err /// Consume the current `EvalAltResult` and return a new one
/// with the specified `Position`.
pub(crate) fn new_position(mut self: Box<Self>, new_position: Position) -> Box<Self> {
self.set_position(new_position);
self
} }
} }

View File

@ -7,7 +7,7 @@ use crate::token::Position;
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
use crate::module::Module; use crate::module::Module;
use crate::stdlib::{borrow::Cow, boxed::Box, iter, vec, vec::Vec}; use crate::stdlib::{borrow::Cow, boxed::Box, iter, string::String, vec::Vec};
/// Type of an entry in the Scope. /// Type of an entry in the Scope.
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
@ -22,7 +22,7 @@ pub enum EntryType {
} }
/// An entry in the Scope. /// An entry in the Scope.
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Entry<'a> { pub struct Entry<'a> {
/// Name of the entry. /// Name of the entry.
pub name: Cow<'a, str>, pub name: Cow<'a, str>,
@ -30,6 +30,8 @@ pub struct Entry<'a> {
pub typ: EntryType, pub typ: EntryType,
/// Current value of the entry. /// Current value of the entry.
pub value: Dynamic, pub value: Dynamic,
/// Alias of the entry.
pub alias: Option<Box<String>>,
/// A constant expression if the initial value matches one of the recognized types. /// A constant expression if the initial value matches one of the recognized types.
pub expr: Option<Box<Expr>>, pub expr: Option<Box<Expr>>,
} }
@ -62,7 +64,7 @@ pub struct Entry<'a> {
/// allowing for automatic _shadowing_. /// allowing for automatic _shadowing_.
/// ///
/// Currently, `Scope` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. /// Currently, `Scope` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`.
#[derive(Debug, Default)] #[derive(Debug, Clone, Default)]
pub struct Scope<'a>(Vec<Entry<'a>>); pub struct Scope<'a>(Vec<Entry<'a>>);
impl<'a> Scope<'a> { impl<'a> Scope<'a> {
@ -175,7 +177,9 @@ impl<'a> Scope<'a> {
/// ///
/// Modules are used for accessing member variables, functions and plugins under a namespace. /// Modules are used for accessing member variables, functions and plugins under a namespace.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub fn push_module<K: Into<Cow<'a, str>>>(&mut self, name: K, value: Module) { pub fn push_module<K: Into<Cow<'a, str>>>(&mut self, name: K, mut value: Module) {
value.index_all_sub_modules();
self.push_dynamic_value( self.push_dynamic_value(
name, name,
EntryType::Module, EntryType::Module,
@ -246,6 +250,7 @@ impl<'a> Scope<'a> {
self.0.push(Entry { self.0.push(Entry {
name: name.into(), name: name.into(),
typ: entry_type, typ: entry_type,
alias: None,
value: value.into(), value: value.into(),
expr, expr,
}); });
@ -410,16 +415,15 @@ impl<'a> Scope<'a> {
/// Get a mutable reference to an entry in the Scope. /// Get a mutable reference to an entry in the Scope.
pub(crate) fn get_mut(&mut self, index: usize) -> (&mut Dynamic, EntryType) { pub(crate) fn get_mut(&mut self, index: usize) -> (&mut Dynamic, EntryType) {
let entry = self.0.get_mut(index).expect("invalid index in Scope"); let entry = self.0.get_mut(index).expect("invalid index in Scope");
// assert_ne!(
// entry.typ,
// EntryType::Constant,
// "get mut of constant entry"
// );
(&mut entry.value, entry.typ) (&mut entry.value, entry.typ)
} }
/// Update the access type of an entry in the Scope.
pub(crate) fn set_entry_alias(&mut self, index: usize, alias: String) {
let entry = self.0.get_mut(index).expect("invalid index in Scope");
entry.alias = Some(Box::new(alias));
}
/// Get an iterator to entries in the Scope. /// Get an iterator to entries in the Scope.
pub(crate) fn into_iter(self) -> impl Iterator<Item = Entry<'a>> { pub(crate) fn into_iter(self) -> impl Iterator<Item = Entry<'a>> {
self.0.into_iter() self.0.into_iter()
@ -437,6 +441,7 @@ impl<'a, K: Into<Cow<'a, str>>> iter::Extend<(K, EntryType, Dynamic)> for Scope<
.extend(iter.into_iter().map(|(name, typ, value)| Entry { .extend(iter.into_iter().map(|(name, typ, value)| Entry {
name: name.into(), name: name.into(),
typ, typ,
alias: None,
value: value.into(), value: value.into(),
expr: None, expr: None,
})); }));

View File

@ -196,6 +196,7 @@ pub enum Token {
XOrAssign, XOrAssign,
ModuloAssign, ModuloAssign,
PowerOfAssign, PowerOfAssign,
Private,
Import, Import,
Export, Export,
As, As,
@ -205,14 +206,14 @@ pub enum Token {
impl Token { impl Token {
/// Get the syntax of the token. /// Get the syntax of the token.
pub fn syntax(&self) -> Cow<str> { pub fn syntax(&self) -> Cow<'static, str> {
use Token::*; use Token::*;
match self { match self {
IntegerConstant(i) => i.to_string().into(), IntegerConstant(i) => i.to_string().into(),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
FloatConstant(f) => f.to_string().into(), FloatConstant(f) => f.to_string().into(),
Identifier(s) => s.into(), Identifier(s) => s.clone().into(),
CharConstant(c) => c.to_string().into(), CharConstant(c) => c.to_string().into(),
LexError(err) => err.to_string().into(), LexError(err) => err.to_string().into(),
@ -279,6 +280,7 @@ impl Token {
ModuloAssign => "%=", ModuloAssign => "%=",
PowerOf => "~", PowerOf => "~",
PowerOfAssign => "~=", PowerOfAssign => "~=",
Private => "private",
Import => "import", Import => "import",
Export => "export", Export => "export",
As => "as", As => "as",
@ -750,6 +752,7 @@ impl<'a> TokenIterator<'a> {
"throw" => Token::Throw, "throw" => Token::Throw,
"for" => Token::For, "for" => Token::For,
"in" => Token::In, "in" => Token::In,
"private" => Token::Private,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
"import" => Token::Import, "import" => Token::Import,

68
src/unsafe.rs Normal file
View File

@ -0,0 +1,68 @@
//! A module containing all unsafe code.
use crate::any::Variant;
use crate::engine::State;
use crate::utils::StaticVec;
use crate::stdlib::{
any::{Any, TypeId},
borrow::Cow,
boxed::Box,
mem, ptr,
string::ToString,
vec::Vec,
};
/// Cast a type into another type.
pub fn unsafe_try_cast<A: Any, B: Any>(a: A) -> Option<B> {
if TypeId::of::<B>() == a.type_id() {
// SAFETY: Just checked we have the right type. We explicitly forget the
// value immediately after moving out, removing any chance of a destructor
// running or value otherwise being used again.
unsafe {
let ret: B = ptr::read(&a as *const _ as *const B);
mem::forget(a);
Some(ret)
}
} else {
None
}
}
/// Cast a Boxed type into another type.
pub fn unsafe_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))
}
} else {
// Return the consumed item for chaining.
Err(item)
}
}
/// A dangerous function that blindly casts a `&str` from one lifetime to a `Cow<str>` of
/// another lifetime. This is mainly used to let us push a block-local variable into the
/// current `Scope` without cloning the variable name. Doing this is safe because all local
/// variables in the `Scope` are cleared out before existing the block.
///
/// Force-casting a local variable lifetime to the current `Scope`'s larger lifetime saves
/// on allocations and string cloning, thus avoids us having to maintain a chain of `Scope`'s.
pub fn unsafe_cast_var_name<'s>(name: &str, state: &State) -> Cow<'s, str> {
// If not at global level, we can force-cast
if state.scope_level > 0 {
// WARNING - force-cast the variable name into the scope's lifetime to avoid cloning it
// this is safe because all local variables are cleared at the end of the block
unsafe { mem::transmute::<_, &'s str>(name) }.into()
} else {
name.to_string().into()
}
}
/// Provide a type instance that is memory-zeroed.
pub fn unsafe_zeroed<T>() -> T {
unsafe { mem::MaybeUninit::zeroed().assume_init() }
}

View File

@ -1,9 +1,12 @@
//! Module containing various utility types and functions. //! Module containing various utility types and functions.
use crate::r#unsafe::unsafe_zeroed;
use crate::stdlib::{ use crate::stdlib::{
any::TypeId, any::TypeId,
fmt, fmt,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
iter::FromIterator,
mem, mem,
vec::Vec, vec::Vec,
}; };
@ -14,30 +17,33 @@ use crate::stdlib::collections::hash_map::DefaultHasher;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use ahash::AHasher; use ahash::AHasher;
/// Calculate a `u64` hash key from a function name and parameter types. #[inline(always)]
/// pub fn EMPTY_TYPE_ID() -> TypeId {
/// Parameter types are passed in via `TypeId` values from an iterator TypeId::of::<()>()
/// 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). /// Calculate a `u64` hash key from a module-qualified function name and parameter types.
pub(crate) fn calc_fn_def(fn_name: &str, num_params: usize) -> u64 { ///
/// Module names are passed in via `&str` references from an iterator.
/// Parameter types are passed in via `TypeId` values from an iterator.
///
/// # Note
///
/// The first module name is skipped. Hashing starts from the _second_ module in the chain.
pub fn calc_fn_spec<'a>(
modules: impl Iterator<Item = &'a str>,
fn_name: &str,
params: impl Iterator<Item = TypeId>,
) -> u64 {
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
let mut s: AHasher = Default::default(); let mut s: AHasher = Default::default();
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
let mut s = DefaultHasher::new(); let mut s = DefaultHasher::new();
// We always skip the first module
modules.skip(1).for_each(|m| m.hash(&mut s));
s.write(fn_name.as_bytes()); s.write(fn_name.as_bytes());
s.write_usize(num_params); params.for_each(|t| t.hash(&mut s));
s.finish() s.finish()
} }
@ -45,8 +51,14 @@ pub(crate) fn calc_fn_def(fn_name: &str, num_params: usize) -> u64 {
/// ///
/// This is essentially a knock-off of the [`staticvec`](https://crates.io/crates/staticvec) crate. /// 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. /// This simplified implementation here is to avoid pulling in another crate.
#[derive(Clone, Default)] ///
pub struct StaticVec<T: Default + Clone> { /// # Safety
///
/// This type uses some unsafe code (mainly to zero out unused array slots) for efficiency.
//
// TODO - remove unsafe code
#[derive(Clone, Hash)]
pub struct StaticVec<T> {
/// Total number of values held. /// Total number of values held.
len: usize, len: usize,
/// Static storage. 4 slots should be enough for most cases - i.e. four levels of indirection. /// Static storage. 4 slots should be enough for most cases - i.e. four levels of indirection.
@ -55,14 +67,52 @@ pub struct StaticVec<T: Default + Clone> {
more: Vec<T>, more: Vec<T>,
} }
impl<T: Default + Clone> StaticVec<T> { impl<T: PartialEq> PartialEq for StaticVec<T> {
fn eq(&self, other: &Self) -> bool {
self.len == other.len && self.list == other.list && self.more == other.more
}
}
impl<T: Eq> Eq for StaticVec<T> {}
impl<T> FromIterator<T> for StaticVec<T> {
fn from_iter<X: IntoIterator<Item = T>>(iter: X) -> Self {
let mut vec = StaticVec::new();
for x in iter {
vec.push(x);
}
vec
}
}
impl<T> Default for StaticVec<T> {
fn default() -> Self {
Self {
len: 0,
list: unsafe_zeroed(),
more: Vec::new(),
}
}
}
impl<T> StaticVec<T> {
/// Create a new `StaticVec`. /// Create a new `StaticVec`.
pub fn new() -> Self { pub fn new() -> Self {
Default::default() Default::default()
} }
/// Push a new value to the end of this `StaticVec`. /// Push a new value to the end of this `StaticVec`.
pub fn push<X: Into<T>>(&mut self, value: X) { pub fn push<X: Into<T>>(&mut self, value: X) {
if self.len >= self.list.len() { if self.len == self.list.len() {
// Move the fixed list to the Vec
for x in 0..self.list.len() {
let def_val: T = unsafe_zeroed();
self.more
.push(mem::replace(self.list.get_mut(x).unwrap(), def_val));
}
self.more.push(value.into());
} else if self.len > self.list.len() {
self.more.push(value.into()); self.more.push(value.into());
} else { } else {
self.list[self.len] = value.into(); self.list[self.len] = value.into();
@ -78,9 +128,19 @@ impl<T: Default + Clone> StaticVec<T> {
let result = if self.len <= 0 { let result = if self.len <= 0 {
panic!("nothing to pop!") panic!("nothing to pop!")
} else if self.len <= self.list.len() { } else if self.len <= self.list.len() {
mem::take(self.list.get_mut(self.len - 1).unwrap()) let def_val: T = unsafe_zeroed();
mem::replace(self.list.get_mut(self.len - 1).unwrap(), def_val)
} else { } else {
self.more.pop().unwrap() let r = self.more.pop().unwrap();
// Move back to the fixed list
if self.more.len() == self.list.len() {
for x in 0..self.list.len() {
self.list[self.list.len() - 1 - x] = self.more.pop().unwrap();
}
}
r
}; };
self.len -= 1; self.len -= 1;
@ -91,38 +151,99 @@ impl<T: Default + Clone> StaticVec<T> {
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.len self.len
} }
/// Get an item at a particular index. /// Get a reference to the item at a particular index.
/// ///
/// # Panics /// # Panics
/// ///
/// Panics if the index is out of bounds. /// Panics if the index is out of bounds.
pub fn get(&self, index: usize) -> &T { pub fn get_ref(&self, index: usize) -> &T {
if index >= self.len { if index >= self.len {
panic!("index OOB in StaticVec"); panic!("index OOB in StaticVec");
} }
if index < self.list.len() { if self.len < self.list.len() {
self.list.get(index).unwrap() self.list.get(index).unwrap()
} else { } else {
self.more.get(index - self.list.len()).unwrap() self.more.get(index).unwrap()
}
}
/// Get a mutable reference to the item at a particular index.
///
/// # Panics
///
/// Panics if the index is out of bounds.
pub fn get_mut(&mut self, index: usize) -> &mut T {
if index >= self.len {
panic!("index OOB in StaticVec");
}
if self.len < self.list.len() {
self.list.get_mut(index).unwrap()
} else {
self.more.get_mut(index).unwrap()
} }
} }
/// Get an iterator to entries in the `StaticVec`. /// Get an iterator to entries in the `StaticVec`.
pub fn iter(&self) -> impl Iterator<Item = &T> { pub fn iter(&self) -> impl Iterator<Item = &T> {
let num = if self.len >= self.list.len() { if self.len > self.list.len() {
self.list.len() self.more.iter()
} else { } else {
self.len self.list[..self.len].iter()
}; }
}
self.list[..num].iter().chain(self.more.iter()) /// Get a mutable iterator to entries in the `StaticVec`.
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
if self.len > self.list.len() {
self.more.iter_mut()
} else {
self.list[..self.len].iter_mut()
}
} }
} }
impl<T: Default + Clone + fmt::Debug> fmt::Debug for StaticVec<T> { impl<T: Copy> StaticVec<T> {
/// Get the 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 self.len < self.list.len() {
*self.list.get(index).unwrap()
} else {
*self.more.get(index).unwrap()
}
}
}
impl<T: fmt::Debug> fmt::Debug for StaticVec<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[ ")?; write!(f, "[ ")?;
self.iter().try_for_each(|v| write!(f, "{:?}, ", v))?; self.iter().try_for_each(|v| write!(f, "{:?}, ", v))?;
write!(f, "]") write!(f, "]")
} }
} }
impl<T> AsRef<[T]> for StaticVec<T> {
fn as_ref(&self) -> &[T] {
if self.len > self.list.len() {
&self.more[..]
} else {
&self.list[..self.len]
}
}
}
impl<T> AsMut<[T]> for StaticVec<T> {
fn as_mut(&mut self) -> &mut [T] {
if self.len > self.list.len() {
&mut self.more[..]
} else {
&mut self.list[..self.len]
}
}
}

View File

@ -41,13 +41,13 @@ fn test_call_fn() -> Result<(), Box<EvalAltResult>> {
", ",
)?; )?;
let r: i64 = engine.call_fn(&mut scope, &ast, "hello", (42 as INT, 123 as INT))?; let r: INT = engine.call_fn(&mut scope, &ast, "hello", (42 as INT, 123 as INT))?;
assert_eq!(r, 165); assert_eq!(r, 165);
let r: i64 = engine.call_fn(&mut scope, &ast, "hello", (123 as INT,))?; let r: INT = engine.call_fn(&mut scope, &ast, "hello", (123 as INT,))?;
assert_eq!(r, 5166); assert_eq!(r, 5166);
let r: i64 = engine.call_fn(&mut scope, &ast, "hello", ())?; let r: INT = engine.call_fn(&mut scope, &ast, "hello", ())?;
assert_eq!(r, 42); assert_eq!(r, 42);
assert_eq!( assert_eq!(
@ -60,6 +60,27 @@ fn test_call_fn() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[test]
fn test_call_fn_private() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
let mut scope = Scope::new();
let ast = engine.compile("fn add(x, n) { x + n }")?;
let r: INT = engine.call_fn(&mut scope, &ast, "add", (40 as INT, 2 as INT))?;
assert_eq!(r, 42);
let ast = engine.compile("private fn add(x, n) { x + n }")?;
assert!(matches!(
*engine.call_fn::<_, INT>(&mut scope, &ast, "add", (40 as INT, 2 as INT))
.expect_err("should error"),
EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "add"
));
Ok(())
}
#[test] #[test]
fn test_anonymous_fn() -> Result<(), Box<EvalAltResult>> { fn test_anonymous_fn() -> Result<(), Box<EvalAltResult>> {
let calc_func = Func::<(INT, INT, INT), INT>::create_from_script( let calc_func = Func::<(INT, INT, INT), INT>::create_from_script(
@ -70,5 +91,16 @@ fn test_anonymous_fn() -> Result<(), Box<EvalAltResult>> {
assert_eq!(calc_func(42, 123, 9)?, 1485); assert_eq!(calc_func(42, 123, 9)?, 1485);
let calc_func = Func::<(INT, INT, INT), INT>::create_from_script(
Engine::new(),
"private fn calc(x, y, z) { (x + y) * z }",
"calc",
)?;
assert!(matches!(
*calc_func(42, 123, 9).expect_err("should error"),
EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "calc"
));
Ok(()) Ok(())
} }

View File

@ -1,4 +1,4 @@
use rhai::{Engine, EvalAltResult, INT}; use rhai::{Engine, EvalAltResult, ParseErrorType, INT};
#[test] #[test]
fn test_constant() -> Result<(), Box<EvalAltResult>> { fn test_constant() -> Result<(), Box<EvalAltResult>> {
@ -8,13 +8,13 @@ fn test_constant() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
*engine.eval::<INT>("const x = 123; x = 42;").expect_err("expects error"), *engine.eval::<INT>("const x = 123; x = 42;").expect_err("expects error"),
EvalAltResult::ErrorAssignmentToConstant(var, _) if var == "x" EvalAltResult::ErrorParsing(err) if err.error_type() == &ParseErrorType::AssignmentToConstant("x".to_string())
)); ));
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
assert!(matches!( assert!(matches!(
*engine.eval::<INT>("const x = [1, 2, 3, 4, 5]; x[2] = 42;").expect_err("expects error"), *engine.eval::<INT>("const x = [1, 2, 3, 4, 5]; x[2] = 42;").expect_err("expects error"),
EvalAltResult::ErrorAssignmentToConstant(var, _) if var == "x" EvalAltResult::ErrorParsing(err) if err.error_type() == &ParseErrorType::AssignmentToConstant("x".to_string())
)); ));
Ok(()) Ok(())

25
tests/functions.rs Normal file
View File

@ -0,0 +1,25 @@
#![cfg(not(feature = "no_function"))]
use rhai::{Engine, EvalAltResult, INT};
#[test]
fn test_functions() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
assert_eq!(engine.eval::<INT>("fn add(x, n) { x + n } add(40, 2)")?, 42);
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<INT>("fn add(x, n) { x + n } let x = 40; x.add(2)")?,
42
);
assert_eq!(engine.eval::<INT>("fn mul2(x) { x * 2 } mul2(21)")?, 42);
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<INT>("fn mul2(x) { x * 2 } let x = 21; x.mul2()")?,
42
);
Ok(())
}

View File

@ -10,8 +10,8 @@ fn test_method_call() -> Result<(), Box<EvalAltResult>> {
} }
impl TestStruct { impl TestStruct {
fn update(&mut self) { fn update(&mut self, n: INT) {
self.x += 1000; self.x += n;
} }
fn new() -> Self { fn new() -> Self {
@ -27,14 +27,23 @@ fn test_method_call() -> Result<(), Box<EvalAltResult>> {
engine.register_fn("new_ts", TestStruct::new); engine.register_fn("new_ts", TestStruct::new);
assert_eq!( assert_eq!(
engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?, engine.eval::<TestStruct>("let x = new_ts(); x.update(1000); x")?,
TestStruct { x: 1001 } TestStruct { x: 1001 }
); );
assert_eq!( assert_eq!(
engine.eval::<TestStruct>("let x = new_ts(); update(x); x")?, engine.eval::<TestStruct>("let x = new_ts(); update(x, 1000); x")?,
TestStruct { x: 1 } TestStruct { x: 1 }
); );
Ok(()) Ok(())
} }
#[test]
fn test_method_call_style() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
assert_eq!(engine.eval::<INT>("let x = -123; x.abs(); x")?, -123);
Ok(())
}

View File

@ -18,7 +18,7 @@ fn test_module_sub_module() -> Result<(), Box<EvalAltResult>> {
let mut sub_module2 = Module::new(); let mut sub_module2 = Module::new();
sub_module2.set_var("answer", 41 as INT); sub_module2.set_var("answer", 41 as INT);
let hash = sub_module2.set_fn_1("inc", |x: INT| Ok(x + 1)); let hash_inc = sub_module2.set_fn_1("inc", |x: INT| Ok(x + 1));
sub_module.set_sub_module("universe", sub_module2); sub_module.set_sub_module("universe", sub_module2);
module.set_sub_module("life", sub_module); module.set_sub_module("life", sub_module);
@ -30,11 +30,11 @@ fn test_module_sub_module() -> Result<(), Box<EvalAltResult>> {
let m2 = m.get_sub_module("universe").unwrap(); let m2 = m.get_sub_module("universe").unwrap();
assert!(m2.contains_var("answer")); assert!(m2.contains_var("answer"));
assert!(m2.contains_fn(hash)); assert!(m2.contains_fn(hash_inc));
assert_eq!(m2.get_var_value::<INT>("answer").unwrap(), 41); assert_eq!(m2.get_var_value::<INT>("answer").unwrap(), 41);
let mut engine = Engine::new(); let engine = Engine::new();
let mut scope = Scope::new(); let mut scope = Scope::new();
scope.push_module("question", module); scope.push_module("question", module);
@ -81,3 +81,87 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[test]
#[cfg(not(feature = "no_function"))]
fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
let mut resolver = rhai::module_resolvers::StaticModuleResolver::new();
let mut sub_module = Module::new();
sub_module.set_var("foo", true);
resolver.insert("another module".to_string(), sub_module);
engine.set_module_resolver(Some(resolver));
let ast = engine.compile(
r#"
// Functions become module functions
fn calc(x) {
x + 1
}
fn add_len(x, y) {
x + len(y)
}
private fn hidden() {
throw "you shouldn't see me!";
}
// Imported modules become sub-modules
import "another module" as extra;
// Variables defined at global level become module variables
const x = 123;
let foo = 41;
let hello;
// Final variable values become constant module variable values
foo = calc(foo);
hello = "hello, " + foo + " worlds!";
export
x as abc,
foo,
hello,
extra as foobar;
"#,
)?;
let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
let mut scope = Scope::new();
scope.push_module("testing", module);
assert_eq!(
engine.eval_expression_with_scope::<INT>(&mut scope, "testing::abc")?,
123
);
assert_eq!(
engine.eval_expression_with_scope::<INT>(&mut scope, "testing::foo")?,
42
);
assert!(engine.eval_expression_with_scope::<bool>(&mut scope, "testing::foobar::foo")?);
assert_eq!(
engine.eval_expression_with_scope::<String>(&mut scope, "testing::hello")?,
"hello, 42 worlds!"
);
assert_eq!(
engine.eval_expression_with_scope::<INT>(&mut scope, "testing::calc(999)")?,
1000
);
assert_eq!(
engine.eval_expression_with_scope::<INT>(
&mut scope,
"testing::add_len(testing::foo, testing::hello)"
)?,
59
);
assert!(matches!(
*engine
.eval_expression_with_scope::<()>(&mut scope, "testing::hidden()")
.expect_err("should error"),
EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "hidden"
));
Ok(())
}

View File

@ -23,7 +23,8 @@ fn test_stack_overflow() -> Result<(), Box<EvalAltResult>> {
) { ) {
Ok(_) => panic!("should be stack overflow"), Ok(_) => panic!("should be stack overflow"),
Err(err) => match *err { Err(err) => match *err {
EvalAltResult::ErrorStackOverflow(_) => (), EvalAltResult::ErrorInFunctionCall(name, _, _)
if name.starts_with("foo > foo > foo") => {}
_ => panic!("should be stack overflow"), _ => panic!("should be stack overflow"),
}, },
} }