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`]
* 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)
* Relatively little `unsafe` code (yes there are some for performance reasons)
* [`no-std`](#optional-features) support
* Support for [function overloading](#function-overloading)
* Support for [operator overloading](#operator-overloading)
* Support for loading external [modules]
* [Function overloading](#function-overloading)
* [Operator overloading](#operator-overloading)
* [Modules]
* Compiled script is [optimized](#script-optimization) for repeat evaluations
* Support for [minimal builds](#minimal-builds) by excluding unneeded language [features](#optional-features)
* Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/)
@ -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.
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
-------
@ -268,6 +270,7 @@ let ast = engine.compile_file("hello_world.rhai".into())?;
### 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`.
Functions declared with `private` are hidden and cannot be called from Rust (see also [modules]).
```rust
// Define functions in a script.
@ -287,6 +290,11 @@ let ast = engine.compile(true,
fn hello() {
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
@ -300,11 +308,15 @@ let result: i64 = engine.call_fn(&mut scope, &ast, "hello", ( String::from("abc"
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// 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
let result: i64 = engine.call_fn(&mut scope, &ast, "hello", () )?
let result: i64 = engine.call_fn(&mut scope, &ast, "hello", () )?;
// ^^ 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
@ -361,7 +373,7 @@ Use `Engine::new_raw` to create a _raw_ `Engine`, in which _nothing_ is added, n
### Packages
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.
```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 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:
@ -391,6 +403,20 @@ The follow packages are available:
| `CorePackage` | Basic essentials | | |
| `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
-------------------------
@ -752,11 +778,17 @@ println!("result: {}", result); // prints 42
let result: f64 = engine.eval("1.0 + 0.0"); // '+' operator for two floats not overloaded
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
script writers expect standard operators to behave in a consistent and predictable manner, and will be annoyed if a calculation
for '+' turns into a subtraction, for example.
Use operator overloading for custom types (described below) only.
Be very careful when overloading built-in operators because script writers expect standard operators to behave in a
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`].
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 |
| `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) |
| `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 |
### Examples
@ -2038,32 +2070,62 @@ for entry in logbook.read().unwrap().iter() {
}
```
Using external modules
----------------------
Modules
-------
[module]: #using-external-modules
[modules]: #using-external-modules
[module]: #modules
[modules]: #modules
Rhai allows organizing code (functions and variables) into _modules_. A module is a single script file
with `export` statements that _exports_ certain global variables and functions as contents of the module.
Rhai allows organizing code (functions, both Rust-based or script-based, and variables) into _modules_.
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
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
import "crypto" as crypto; // import the script file 'crypto.rhai' as a module
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
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.
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
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
// 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
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;
```
### 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
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.
| Module Resolver | Description |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `FileModuleResolver` | The default module resolution service, not available under the [`no_std`] feature. Loads a script file (based off the current directory) with `.rhai` extension.<br/>The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function. |
| `StaticModuleResolver` | Loads modules that are statically added. This can be used when the [`no_std`] feature is turned on. |
| 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.<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. |
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 std::rc::Rc;
fn main() -> Result<(), Box<EvalAltResult>> {
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"))]
use rhai::OptimizationLevel;
use std::{
io::{stdin, stdout, Write},
iter,
};
use std::io::{stdin, stdout, Write};
fn print_error(input: &str, err: EvalAltResult) {
let lines: Vec<_> = input.trim().split('\n').collect();

View File

@ -3,7 +3,7 @@ use rhai::{Engine, EvalAltResult};
#[cfg(not(feature = "no_optimize"))]
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_line(lines: &[&str], line: usize, pos: usize, err: &str) {

View File

@ -3,8 +3,6 @@
const target = 30;
let now = timestamp();
fn fib(n) {
if n < 2 {
n
@ -15,8 +13,14 @@ fn fib(n) {
print("Ready... Go!");
let now = timestamp();
let result = fib(target);
print("Finished. Run time = " + now.elapsed() + " seconds.");
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("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.
use crate::parser::INT;
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
#[cfg(not(feature = "no_module"))]
use crate::module::Module;
use crate::parser::INT;
#[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT;
@ -18,7 +19,7 @@ use crate::stdlib::{
any::{type_name, Any, TypeId},
boxed::Box,
collections::HashMap,
fmt,
fmt, mem, ptr,
string::String,
vec::Vec,
};
@ -38,6 +39,9 @@ pub trait Variant: Any {
/// Convert this `Variant` trait object to `&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.
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 {
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 {
type_name::<T>()
}
@ -86,6 +93,9 @@ pub trait Variant: Any + Send + Sync {
/// Convert this `Variant` trait object to `&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.
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 {
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 {
type_name::<T>()
}
@ -133,6 +146,8 @@ impl dyn Variant {
pub struct Dynamic(pub(crate) Union);
/// Internal `Dynamic` representation.
///
/// Most variants are boxed to reduce the size.
pub enum Union {
Unit(()),
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 {
/// 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`.
/// 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.
@ -347,17 +353,17 @@ impl Dynamic {
let mut var = Box::new(value);
var = match cast_box::<_, Dynamic>(var) {
var = match unsafe_cast_box::<_, Dynamic>(var) {
Ok(d) => return *d,
Err(var) => var,
};
var = match cast_box::<_, String>(var) {
var = match unsafe_cast_box::<_, String>(var) {
Ok(s) => return Self(Union::Str(s)),
Err(var) => var,
};
#[cfg(not(feature = "no_index"))]
{
var = match cast_box::<_, Array>(var) {
var = match unsafe_cast_box::<_, Array>(var) {
Ok(array) => return Self(Union::Array(array)),
Err(var) => var,
};
@ -365,7 +371,7 @@ impl Dynamic {
#[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)),
Err(var) => var,
}
@ -388,26 +394,26 @@ impl Dynamic {
///
/// 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>() {
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 {
Union::Unit(ref value) => (value as &dyn Any).downcast_ref::<T>().cloned(),
Union::Bool(ref value) => (value as &dyn Any).downcast_ref::<T>().cloned(),
Union::Str(value) => cast_box::<_, T>(value).ok().map(|v| *v),
Union::Char(ref value) => (value as &dyn Any).downcast_ref::<T>().cloned(),
Union::Int(ref value) => (value as &dyn Any).downcast_ref::<T>().cloned(),
Union::Unit(value) => unsafe_try_cast(value),
Union::Bool(value) => unsafe_try_cast(value),
Union::Str(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
Union::Char(value) => unsafe_try_cast(value),
Union::Int(value) => unsafe_try_cast(value),
#[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"))]
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"))]
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"))]
Union::Module(value) => cast_box::<_, T>(value).ok().map(|v| *v),
Union::Variant(value) => value.as_any().downcast_ref::<T>().cloned(),
Union::Module(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(),
}
}
@ -431,24 +437,24 @@ impl Dynamic {
//self.try_cast::<T>().unwrap()
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 {
Union::Unit(ref value) => (value as &dyn Any).downcast_ref::<T>().unwrap().clone(),
Union::Bool(ref value) => (value as &dyn Any).downcast_ref::<T>().unwrap().clone(),
Union::Str(value) => *cast_box::<_, T>(value).unwrap(),
Union::Char(ref value) => (value as &dyn Any).downcast_ref::<T>().unwrap().clone(),
Union::Int(ref value) => (value as &dyn Any).downcast_ref::<T>().unwrap().clone(),
Union::Unit(value) => unsafe_try_cast(value).unwrap(),
Union::Bool(value) => unsafe_try_cast(value).unwrap(),
Union::Str(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
Union::Char(value) => unsafe_try_cast(value).unwrap(),
Union::Int(value) => unsafe_try_cast(value).unwrap(),
#[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"))]
Union::Array(value) => *cast_box::<_, T>(value).unwrap(),
Union::Array(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
#[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"))]
Union::Module(value) => *cast_box::<_, T>(value).unwrap(),
Union::Variant(value) => value.as_any().downcast_ref::<T>().unwrap().clone(),
Union::Module(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
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::error::ParseError;
use crate::fn_call::FuncArgs;
use crate::fn_native::{
IteratorCallback, ObjectGetCallback, ObjectIndexerCallback, ObjectSetCallback,
};
use crate::fn_register::RegisterFn;
use crate::optimize::{optimize_into_ast, OptimizationLevel};
use crate::parser::{parse, parse_global_expr, AST};
use crate::result::EvalAltResult;
use crate::scope::Scope;
use crate::token::{lex, Position};
use crate::utils::StaticVec;
#[cfg(not(feature = "no_object"))]
use crate::engine::Map;
@ -20,58 +24,11 @@ use crate::stdlib::{
collections::HashMap,
mem,
string::{String, ToString},
vec::Vec,
};
#[cfg(not(feature = "no_std"))]
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
impl 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`.
/// This is an advanced feature.
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`.
@ -385,6 +342,7 @@ impl Engine {
}
/// 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
/// when using `OptimizationLevel::Full`.
///
@ -422,18 +380,71 @@ impl Engine {
/// # }
/// ```
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(
&self,
scope: &Scope,
script: &str,
scripts: &[&str],
optimization_level: OptimizationLevel,
) -> Result<AST, Box<ParseError>> {
let scripts = [script];
let stream = lex(&scripts);
let stream = lex(scripts);
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.
///
/// The scope is useful for passing constants into the script for optimization
/// when using `OptimizationLevel::Full`.
///
@ -738,8 +750,11 @@ impl Engine {
script: &str,
) -> Result<T, Box<EvalAltResult>> {
// Since the AST will be thrown away afterwards, don't bother to optimize it
let ast =
self.compile_with_scope_and_optimization_level(scope, script, OptimizationLevel::None)?;
let ast = self.compile_with_scope_and_optimization_level(
scope,
&[script],
OptimizationLevel::None,
)?;
self.eval_ast_with_scope(scope, &ast)
}
@ -856,7 +871,7 @@ impl Engine {
return result.try_cast::<T>().ok_or_else(|| {
Box::new(EvalAltResult::ErrorMismatchOutputType(
return_type.to_string(),
return_type.into(),
Position::none(),
))
});
@ -867,12 +882,12 @@ impl Engine {
scope: &mut Scope,
ast: &AST,
) -> Result<Dynamic, Box<EvalAltResult>> {
let mut state = State::new();
let mut state = State::new(ast.fn_lib());
ast.statements()
.iter()
.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 {
EvalAltResult::Return(out, _) => Ok(out),
@ -932,12 +947,12 @@ impl Engine {
scope: &mut Scope,
ast: &AST,
) -> Result<(), Box<EvalAltResult>> {
let mut state = State::new();
let mut state = State::new(ast.fn_lib());
ast.statements()
.iter()
.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(
|err| match *err {
@ -992,15 +1007,18 @@ impl Engine {
args: A,
) -> Result<T, Box<EvalAltResult>> {
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 pos = Position::none();
let fn_def = fn_lib
.get_function(name, args.len())
.ok_or_else(|| Box::new(EvalAltResult::ErrorFunctionNotFound(name.to_string(), pos)))?;
.get_function_by_signature(name, args.len(), true)
.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());

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};
/// Error when tokenizing the script text.
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub enum LexError {
/// An unexpected character is encountered when tokenizing the script text.
UnexpectedChar(char),
@ -44,7 +44,7 @@ impl fmt::Display for LexError {
/// 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
/// 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 {
/// Error in the script text. Wrapped value is the error message.
BadInput(String),
@ -98,8 +98,16 @@ pub enum ParseErrorType {
///
/// Never appears under the `no_function` feature.
FnMissingBody(String),
/// Assignment to an inappropriate LHS (left-hand-side) expression.
AssignmentToInvalidLHS,
/// An export statement has duplicated names.
///
/// 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.
AssignmentToConstant(String),
/// Break statement not inside a loop.
@ -114,7 +122,7 @@ impl ParseErrorType {
}
/// Error when parsing a script.
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub struct ParseError(pub(crate) ParseErrorType, pub(crate) Position);
impl ParseError {
@ -147,8 +155,10 @@ impl ParseError {
ParseErrorType::FnDuplicatedParam(_,_) => "Duplicated parameters in 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::AssignmentToInvalidLHS => "Cannot assign to this expression",
ParseErrorType::AssignmentToConstant(_) => "Cannot assign to a constant variable.",
ParseErrorType::DuplicatedExport(_) => "Duplicated variable/function in export statement",
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"
}
}
@ -193,6 +203,12 @@ impl fmt::Display for ParseError {
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::AssignmentToConstant(s) if s.is_empty() => {

View File

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

View File

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

View File

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

View File

@ -1,61 +1,69 @@
//! Module defining external-loaded modules for Rhai.
#![cfg(not(feature = "no_module"))]
use crate::any::{Dynamic, Variant};
use crate::calc_fn_hash;
use crate::engine::{Engine, FnAny, FnCallArgs, FunctionsLib};
use crate::parser::{FnDef, AST};
use crate::engine::{Engine, FunctionsLib};
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::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
use crate::token::Position;
use crate::token::Token;
use crate::utils::StaticVec;
use crate::token::{Position, Token};
use crate::utils::{StaticVec, EMPTY_TYPE_ID};
use crate::stdlib::{
any::TypeId,
boxed::Box,
collections::HashMap,
fmt, mem,
fmt,
iter::{empty, repeat},
mem,
num::NonZeroUsize,
ops::{Deref, DerefMut},
rc::Rc,
string::{String, ToString},
sync::Arc,
vec,
vec::Vec,
};
/// A trait that encapsulates a module resolution service.
pub trait ModuleResolver {
/// Resolve a module based on a path string.
fn resolve(
&self,
engine: &Engine,
path: &str,
pos: Position,
) -> Result<Module, Box<EvalAltResult>>;
}
/// Default function access mode.
const DEF_ACCESS: FnAccess = FnAccess::Public;
/// 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,
/// external Rust functions, and script-defined functions.
///
/// Not available under the `no_module` feature.
#[derive(Default, Clone)]
#[derive(Clone, Default)]
pub struct Module {
/// Sub-modules.
modules: HashMap<String, Module>,
/// Module variables, including sub-modules.
/// Module variables.
variables: HashMap<String, Dynamic>,
/// Flattened collection of all module variables, including those in sub-modules.
all_variables: HashMap<u64, Dynamic>,
/// External Rust functions.
#[cfg(not(feature = "sync"))]
functions: HashMap<u64, Rc<Box<FnAny>>>,
/// External Rust functions.
#[cfg(feature = "sync")]
functions: HashMap<u64, Arc<Box<FnAny>>>,
functions: HashMap<u64, (String, FnAccess, Vec<TypeId>, SharedNativeFunction)>,
/// Flattened collection of all external Rust functions, including those in sub-modules.
all_functions: HashMap<u64, SharedNativeFunction>,
/// Script-defined functions.
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 {
@ -86,6 +94,24 @@ impl Module {
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?
///
/// # Examples
@ -113,7 +139,7 @@ impl Module {
/// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42);
/// ```
pub fn get_var_value<T: Variant + Clone>(&self, name: &str) -> Option<T> {
self.get_var(name).and_then(|v| v.try_cast::<T>())
self.get_var(name).and_then(Dynamic::try_cast::<T>)
}
/// Get a module variable as a `Dynamic`.
@ -131,11 +157,6 @@ impl Module {
self.variables.get(name).cloned()
}
/// Get a mutable reference to a module variable.
pub fn get_var_mut(&mut self, name: &str) -> Option<&mut Dynamic> {
self.variables.get_mut(name)
}
/// Set a variable into the module.
///
/// If there is an existing variable of the same name, it is replaced.
@ -149,21 +170,22 @@ impl Module {
/// module.set_var("answer", 42_i64);
/// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42);
/// ```
pub fn set_var<K: Into<String>, T: Into<Dynamic>>(&mut self, name: K, value: T) {
self.variables.insert(name.into(), value.into());
pub fn set_var<K: Into<String>, T: Variant + Clone>(&mut self, name: K, value: T) {
self.variables.insert(name.into(), Dynamic::from(value));
}
/// 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(
&mut self,
name: &str,
modules: &StaticVec<(String, Position)>,
hash_var: u64,
pos: Position,
) -> Result<&mut Dynamic, Box<EvalAltResult>> {
Ok(self
.get_qualified_module_mut(modules)?
.get_var_mut(name)
.ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))?)
self.all_variables
.get_mut(&hash_var)
.ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.to_string(), pos)))
}
/// Does a sub-module exist in the module?
@ -232,26 +254,6 @@ impl Module {
self.modules.insert(name.into(), sub_module.into());
}
/// Get a mutable reference to a modules chain.
/// The first module is always skipped and assumed to be the same as `self`.
pub(crate) fn get_qualified_module_mut(
&mut self,
modules: &StaticVec<(String, Position)>,
) -> Result<&mut Module, Box<EvalAltResult>> {
let mut drain = modules.iter();
drain.next().unwrap(); // Skip first module
let mut module = self;
for (id, id_pos) in drain {
module = module
.get_sub_module_mut(id)
.ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *id_pos)))?;
}
Ok(module)
}
/// Does the particular Rust function exist in the module?
///
/// The `u64` hash is calculated by the function `crate::calc_fn_hash`.
@ -266,22 +268,34 @@ impl Module {
/// let hash = module.set_fn_0("calc", || Ok(42_i64));
/// assert!(module.contains_fn(hash));
/// ```
pub fn contains_fn(&self, hash: u64) -> bool {
self.functions.contains_key(&hash)
pub fn contains_fn(&self, hash_fn: u64) -> bool {
self.functions.contains_key(&hash_fn)
}
/// Set a Rust function into the module, returning a hash key.
///
/// If there is an existing Rust function of the same hash, it is replaced.
pub fn set_fn(&mut self, fn_name: &str, params: &[TypeId], func: Box<FnAny>) -> u64 {
let hash = calc_fn_hash(fn_name, params.iter().cloned());
pub fn set_fn(
&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"))]
self.functions.insert(hash, Rc::new(func));
let func = Rc::new(f);
#[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.
@ -297,19 +311,15 @@ impl Module {
/// let hash = module.set_fn_0("calc", || Ok(42_i64));
/// assert!(module.get_fn(hash).is_some());
/// ```
pub fn set_fn_0<T: Into<Dynamic>>(
pub fn set_fn_0<K: Into<String>, T: Variant + Clone>(
&mut self,
fn_name: &str,
name: K,
#[cfg(not(feature = "sync"))] func: impl Fn() -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn() -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 {
let f = move |_: &mut FnCallArgs, pos| {
func()
.map(|v| v.into())
.map_err(|err| EvalAltResult::set_position(err, pos))
};
let arg_types = &[];
self.set_fn(fn_name, arg_types, Box::new(f))
let f = move |_: &mut FnCallArgs| func().map(Dynamic::from);
let arg_types = [];
self.set_fn(name.into(), Pure, DEF_ACCESS, &arg_types, Box::new(f))
}
/// 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));
/// 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,
fn_name: &str,
name: K,
#[cfg(not(feature = "sync"))] func: impl Fn(A) -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn(A) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 {
let f = move |args: &mut FnCallArgs, pos| {
func(mem::take(args[0]).cast::<A>())
.map(|v| v.into())
.map_err(|err| EvalAltResult::set_position(err, pos))
};
let arg_types = &[TypeId::of::<A>()];
self.set_fn(fn_name, arg_types, Box::new(f))
let f =
move |args: &mut FnCallArgs| func(mem::take(args[0]).cast::<A>()).map(Dynamic::from);
let arg_types = [TypeId::of::<A>()];
self.set_fn(name.into(), Pure, DEF_ACCESS, &arg_types, Box::new(f))
}
/// 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) });
/// 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,
fn_name: &str,
name: K,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A) -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn(&mut A) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 {
let f = move |args: &mut FnCallArgs, pos| {
func(args[0].downcast_mut::<A>().unwrap())
.map(|v| v.into())
.map_err(|err| EvalAltResult::set_position(err, pos))
let f = move |args: &mut FnCallArgs| {
func(args[0].downcast_mut::<A>().unwrap()).map(Dynamic::from)
};
let arg_types = &[TypeId::of::<A>()];
self.set_fn(fn_name, arg_types, Box::new(f))
let arg_types = [TypeId::of::<A>()];
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.
@ -383,22 +388,20 @@ impl Module {
/// });
/// 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,
fn_name: &str,
name: K,
#[cfg(not(feature = "sync"))] func: impl Fn(A, B) -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn(A, B) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 {
let f = move |args: &mut FnCallArgs, pos| {
let f = move |args: &mut FnCallArgs| {
let a = mem::take(args[0]).cast::<A>();
let b = mem::take(args[1]).cast::<B>();
func(a, b)
.map(|v| v.into())
.map_err(|err| EvalAltResult::set_position(err, pos))
func(a, b).map(Dynamic::from)
};
let arg_types = &[TypeId::of::<A>(), TypeId::of::<B>()];
self.set_fn(fn_name, arg_types, Box::new(f))
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>()];
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,
@ -415,22 +418,25 @@ impl Module {
/// });
/// 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,
fn_name: &str,
name: K,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 {
let f = move |args: &mut FnCallArgs, pos| {
let f = move |args: &mut FnCallArgs| {
let b = mem::take(args[1]).cast::<B>();
let a = args[0].downcast_mut::<A>().unwrap();
func(a, b)
.map(|v| v.into())
.map_err(|err| EvalAltResult::set_position(err, pos))
func(a, b).map(Dynamic::from)
};
let arg_types = &[TypeId::of::<A>(), TypeId::of::<B>()];
self.set_fn(fn_name, arg_types, Box::new(f))
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>()];
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.
@ -449,27 +455,26 @@ impl Module {
/// assert!(module.get_fn(hash).is_some());
/// ```
pub fn set_fn_3<
K: Into<String>,
A: Variant + Clone,
B: Variant + Clone,
C: Variant + Clone,
T: Into<Dynamic>,
T: Variant + Clone,
>(
&mut self,
fn_name: &str,
name: K,
#[cfg(not(feature = "sync"))] func: impl Fn(A, B, C) -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn(A, B, C) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 {
let f = move |args: &mut FnCallArgs, pos| {
let f = move |args: &mut FnCallArgs| {
let a = mem::take(args[0]).cast::<A>();
let b = mem::take(args[1]).cast::<B>();
let c = mem::take(args[2]).cast::<C>();
func(a, b, c)
.map(|v| v.into())
.map_err(|err| EvalAltResult::set_position(err, pos))
func(a, b, c).map(Dynamic::from)
};
let arg_types = &[TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn(fn_name, arg_types, Box::new(f))
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
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,
@ -489,27 +494,26 @@ impl Module {
/// assert!(module.get_fn(hash).is_some());
/// ```
pub fn set_fn_3_mut<
K: Into<String>,
A: Variant + Clone,
B: Variant + Clone,
C: Variant + Clone,
T: Into<Dynamic>,
T: Variant + Clone,
>(
&mut self,
fn_name: &str,
name: K,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B, C) -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn(&mut A, B, C) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 {
let f = move |args: &mut FnCallArgs, pos| {
let f = move |args: &mut FnCallArgs| {
let b = mem::take(args[1]).cast::<B>();
let c = mem::take(args[2]).cast::<C>();
let a = args[0].downcast_mut::<A>().unwrap();
func(a, b, c)
.map(|v| v.into())
.map_err(|err| EvalAltResult::set_position(err, pos))
func(a, b, c).map(Dynamic::from)
};
let arg_types = &[TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn(fn_name, arg_types, Box::new(f))
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn(name.into(), Method, DEF_ACCESS, &arg_types, Box::new(f))
}
/// Get a Rust function.
@ -526,8 +530,8 @@ impl Module {
/// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1));
/// assert!(module.get_fn(hash).is_some());
/// ```
pub fn get_fn(&self, hash: u64) -> Option<&Box<FnAny>> {
self.functions.get(&hash).map(|v| v.as_ref())
pub fn get_fn(&self, hash_fn: u64) -> Option<&Box<dyn NativeCallable>> {
self.functions.get(&hash_fn).map(|(_, _, _, v)| v.as_ref())
}
/// Get a modules-qualified function.
@ -537,52 +541,24 @@ impl Module {
pub(crate) fn get_qualified_fn(
&mut self,
name: &str,
hash: u64,
modules: &StaticVec<(String, Position)>,
pos: Position,
) -> Result<&Box<FnAny>, Box<EvalAltResult>> {
Ok(self
.get_qualified_module_mut(modules)?
.get_fn(hash)
hash_fn_native: u64,
) -> Result<&Box<dyn NativeCallable>, Box<EvalAltResult>> {
self.all_functions
.get(&hash_fn_native)
.map(|f| f.as_ref())
.ok_or_else(|| {
let mut fn_name: String = Default::default();
modules.iter().for_each(|(n, _)| {
fn_name.push_str(n);
fn_name.push_str(Token::DoubleColon.syntax().as_ref());
});
fn_name.push_str(name);
Box::new(EvalAltResult::ErrorFunctionNotFound(fn_name, pos))
})?)
Box::new(EvalAltResult::ErrorFunctionNotFound(
name.to_string(),
Position::none(),
))
})
}
/// Get the script-defined functions.
/// Get a modules-qualified script-defined functions.
///
/// # Examples
///
/// ```
/// use rhai::Module;
///
/// let mut module = Module::new();
/// assert_eq!(module.get_fn_lib().len(), 0);
/// ```
pub fn get_fn_lib(&self) -> &FunctionsLib {
&self.fn_lib
}
/// Get a modules-qualified functions library.
pub(crate) fn get_qualified_fn_lib(
&mut self,
name: &str,
args: usize,
modules: &StaticVec<(String, Position)>,
) -> Result<Option<&FnDef>, Box<EvalAltResult>> {
Ok(self
.get_qualified_module_mut(modules)?
.fn_lib
.get_function(name, args))
/// 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)
}
/// Create a new `Module` by evaluating an `AST`.
@ -591,20 +567,18 @@ impl Module {
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// use rhai::{Engine, Module};
/// use rhai::{Engine, Module, Scope};
///
/// let engine = Engine::new();
/// let ast = engine.compile("let answer = 42;")?;
/// let module = Module::eval_ast_as_new(&ast, &engine)?;
/// let ast = engine.compile("let answer = 42; export answer;")?;
/// let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
/// assert!(module.contains_var("answer"));
/// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42);
/// # Ok(())
/// # }
/// ```
pub fn eval_ast_as_new(ast: &AST, engine: &Engine) -> FuncReturn<Self> {
// Use new scope
let mut scope = Scope::new();
#[cfg(not(feature = "no_module"))]
pub fn eval_ast_as_new(mut scope: Scope, ast: &AST, engine: &Engine) -> FuncReturn<Self> {
// Run the script
engine.eval_ast_with_scope_raw(&mut scope, &ast)?;
@ -613,19 +587,21 @@ impl Module {
scope.into_iter().for_each(
|ScopeEntry {
name, typ, value, ..
typ, value, alias, ..
}| {
match typ {
// Variables left in the scope become module variables
ScopeEntryType::Normal | ScopeEntryType::Constant => {
module.variables.insert(name.into_owned(), value);
// Variables with an alias left in the scope become module variables
ScopeEntryType::Normal | ScopeEntryType::Constant if alias.is_some() => {
module.variables.insert(*alias.unwrap(), value);
}
// Modules left in the scope become sub-modules
ScopeEntryType::Module => {
ScopeEntryType::Module if alias.is_some() => {
module
.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)
}
/// 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.
#[cfg(not(feature = "no_module"))]
pub mod resolvers {
#[cfg(not(feature = "no_std"))]
pub use super::file::FileModuleResolver;
@ -644,6 +806,7 @@ pub mod resolvers {
}
/// Script file-based module resolver.
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_std"))]
mod file {
use super::*;
@ -669,12 +832,18 @@ mod file {
/// let mut engine = Engine::new();
/// 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 {
path: PathBuf,
extension: String,
}
impl Default for FileModuleResolver {
fn default() -> Self {
Self::new_with_path(PathBuf::default())
}
}
impl FileModuleResolver {
/// Create a new `FileModuleResolver` with a specific base path.
///
@ -740,12 +909,23 @@ mod file {
pub fn new() -> Self {
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 {
fn resolve(
&self,
engine: &Engine,
scope: Scope,
path: &str,
pos: Position,
) -> Result<Module, Box<EvalAltResult>> {
@ -757,15 +937,15 @@ mod file {
// Compile it
let ast = engine
.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)
.map_err(|err| EvalAltResult::set_position(err, pos))
Module::eval_ast_as_new(scope, &ast, engine).map_err(|err| err.new_position(pos))
}
}
}
/// Static module resolver.
#[cfg(not(feature = "no_module"))]
mod stat {
use super::*;
@ -828,13 +1008,14 @@ mod stat {
fn resolve(
&self,
_: &Engine,
_: Scope,
path: &str,
pos: Position,
) -> Result<Module, Box<EvalAltResult>> {
self.0
.get(path)
.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::calc_fn_hash;
use crate::engine::{
Engine, FnAny, FnCallArgs, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT,
KEYWORD_TYPE_OF,
Engine, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, 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::result::EvalAltResult;
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
@ -13,6 +14,7 @@ use crate::token::Position;
use crate::stdlib::{
boxed::Box,
collections::HashMap,
iter::empty,
string::{String, ToString},
vec,
vec::Vec,
@ -94,7 +96,7 @@ impl<'a> State<'a> {
}
/// Add a new constant to the list.
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.
pub fn find_constant(&self, name: &str) -> Option<&Expr> {
@ -110,88 +112,84 @@ impl<'a> State<'a> {
/// Call a registered function
fn call_fn(
packages: &Vec<PackageLibrary>,
functions: &HashMap<u64, Box<FnAny>>,
packages: &PackagesCollection,
global_module: &Module,
fn_name: &str,
args: &mut FnCallArgs,
pos: Position,
) -> Result<Option<Dynamic>, Box<EvalAltResult>> {
// 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
.get(&hash)
.or_else(|| {
packages
.iter()
.find(|p| p.functions.contains_key(&hash))
.and_then(|p| p.functions.get(&hash))
})
.map(|func| func(args, pos))
global_module
.get_fn(hash)
.or_else(|| packages.get_fn(hash))
.map(|func| func.call(args))
.transpose()
.map_err(|err| err.new_position(pos))
}
/// Optimize a statement.
fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -> Stmt {
match stmt {
// 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();
let pos = expr.position();
let expr = optimize_expr(*expr, state);
let pos = x.0.position();
let expr = optimize_expr(x.0, state);
if preserve_result {
// -> { 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 {
// -> expr
Stmt::Expr(Box::new(expr))
}
}
// 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
Expr::False(pos) => {
state.set_dirty();
Stmt::Noop(pos)
}
// 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 }
expr => Stmt::IfThenElse(
Box::new(optimize_expr(expr, state)),
Box::new(optimize_stmt(*if_block, state, true)),
expr => Stmt::IfThenElse(Box::new((
optimize_expr(expr, state),
optimize_stmt(x.1, state, true),
None,
),
))),
},
// 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
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
Expr::True(_) => optimize_stmt(*if_block, state, true),
Expr::True(_) => optimize_stmt(x.1, state, true),
// if expr { if_block } else { else_block }
expr => Stmt::IfThenElse(
Box::new(optimize_expr(expr, state)),
Box::new(optimize_stmt(*if_block, state, true)),
match optimize_stmt(*else_block, state, true) {
expr => Stmt::IfThenElse(Box::new((
optimize_expr(expr, state),
optimize_stmt(x.1, state, true),
match optimize_stmt(x.2.unwrap(), state, true) {
Stmt::Noop(_) => None, // Noop -> no else block
stmt => Some(Box::new(stmt)),
stmt => Some(stmt),
},
),
))),
},
// while expr { block }
Stmt::While(expr, block) => match *expr {
Stmt::While(x) => match x.0 {
// while false { block } -> Noop
Expr::False(pos) => {
state.set_dirty();
Stmt::Noop(pos)
}
// 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 }
expr => match optimize_stmt(*block, state, false) {
expr => match optimize_stmt(x.1, state, false) {
// while expr { break; } -> { expr; }
Stmt::Break(pos) => {
// 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 {
statements.push(Stmt::Noop(pos))
}
Stmt::Block(statements, pos)
Stmt::Block(Box::new((statements, pos)))
}
// 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 }
@ -218,38 +216,40 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
stmt => Stmt::Loop(Box::new(stmt)),
},
// for id in expr { block }
Stmt::For(id, expr, block) => Stmt::For(
id,
Box::new(optimize_expr(*expr, state)),
Box::new(optimize_stmt(*block, state, false)),
),
Stmt::For(x) => Stmt::For(Box::new((
x.0,
optimize_expr(x.1, state),
optimize_stmt(x.2, state, false),
))),
// let id = expr;
Stmt::Let(id, Some(expr), pos) => {
Stmt::Let(id, Some(Box::new(optimize_expr(*expr, state))), pos)
Stmt::Let(x) if x.1.is_some() => {
Stmt::Let(Box::new((x.0, Some(optimize_expr(x.1.unwrap(), state)))))
}
// let id;
Stmt::Let(_, None, _) => stmt,
stmt @ Stmt::Let(_) => stmt,
// 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 }
Stmt::Block(block, pos) => {
let orig_len = block.len(); // Original number of statements in the block, for change detection
Stmt::Block(x) => {
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 pos = x.1;
// Optimize each statement in the block
let mut result: Vec<_> = block
.into_iter()
.map(|stmt| match stmt {
// Add constant into the state
Stmt::Const(name, value, pos) => {
state.push_constant(&name, *value);
state.set_dirty();
Stmt::Noop(pos) // No need to keep constants
}
// Optimize the statement
_ => optimize_stmt(stmt, state, preserve_result),
})
.collect();
let mut result: Vec<_> =
x.0.into_iter()
.map(|stmt| match stmt {
// Add constant into the state
Stmt::Const(v) => {
let ((name, pos), expr) = *v;
state.push_constant(&name, expr);
state.set_dirty();
Stmt::Noop(pos) // No need to keep constants
}
// Optimize the statement
_ => optimize_stmt(stmt, state, preserve_result),
})
.collect();
// Remove all raw expression statements that are pure except for the very last statement
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() {
match expr {
Stmt::Let(_, None, _) => removed = true,
Stmt::Let(_, Some(val_expr), _) => removed = val_expr.is_pure(),
Stmt::Import(expr, _, _) => removed = expr.is_pure(),
Stmt::Let(x) if x.1.is_none() => removed = true,
Stmt::Let(x) if x.1.is_some() => removed = x.1.unwrap().is_pure(),
Stmt::Import(x) => removed = x.0.is_pure(),
_ => {
result.push(expr);
break;
@ -301,7 +301,7 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
}
match stmt {
Stmt::ReturnWithVal(_, _, _) | Stmt::Break(_) => {
Stmt::ReturnWithVal(_) | Stmt::Break(_) => {
dead_code = true;
}
_ => (),
@ -325,20 +325,20 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
Stmt::Noop(pos)
}
// 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
[_] => {
state.set_dirty();
result.remove(0)
}
_ => Stmt::Block(result, pos),
_ => Stmt::Block(Box::new((result, pos))),
}
}
// expr;
Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))),
// return expr;
Stmt::ReturnWithVal(Some(expr), is_return, pos) => {
Stmt::ReturnWithVal(Some(Box::new(optimize_expr(*expr, state))), is_return, pos)
Stmt::ReturnWithVal(x) if x.1.is_some() => {
Stmt::ReturnWithVal(Box::new((x.0, Some(optimize_expr(x.1.unwrap(), state)))))
}
// All other statements - skip
stmt => stmt,
@ -352,11 +352,11 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
match expr {
// ( stmt )
Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, state, true) {
Expr::Stmt(x) => match optimize_stmt(x.0, state, true) {
// ( Noop ) -> ()
Stmt::Noop(_) => {
state.set_dirty();
Expr::Unit(pos)
Expr::Unit(x.1)
}
// ( expr ) -> expr
Stmt::Expr(expr) => {
@ -364,150 +364,129 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
*expr
}
// ( stmt )
stmt => Expr::Stmt(Box::new(stmt), pos),
stmt => Expr::Stmt(Box::new((stmt, x.1))),
},
// id = expr
Expr::Assignment(id, expr, pos) => match *expr {
Expr::Assignment(x) => match x.1 {
//id = id2 = expr2
Expr::Assignment(id2, expr2, pos2) => match (*id, *id2) {
Expr::Assignment(x2) => match (x.0, x2.0) {
// var = var = expr2 -> var = expr2
(Expr::Variable(var, None, sp, _), Expr::Variable(var2, None, sp2, _))
if var == var2 && sp == sp2 =>
(Expr::Variable(a), Expr::Variable(b))
if a.1.is_none() && b.1.is_none() && a.0 == b.0 && a.3 == b.3 =>
{
// Assignment to the same variable - fold
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) => Expr::Assignment(
Box::new(id1),
Box::new(Expr::Assignment(
Box::new(id2),
Box::new(optimize_expr(*expr2, state)),
pos2,
)),
pos,
),
(id1, id2) => {
Expr::Assignment(Box::new((
id1, Expr::Assignment(Box::new((id2, optimize_expr(x2.1, state), x2.2))), x.2,
)))
}
},
// 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
#[cfg(not(feature = "no_object"))]
Expr::Dot(lhs, rhs, pos) => match (*lhs, *rhs) {
Expr::Dot(x) => match (x.0, x.1) {
// 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.
// All other items can be thrown away.
state.set_dirty();
items.into_iter().find(|(name, _, _)| name == &s)
.map(|(_, expr, _)| expr.set_position(pos))
let pos = m.1;
m.0.into_iter().find(|((name, _), _)| name == prop)
.map(|(_, expr)| expr.set_position(pos))
.unwrap_or_else(|| Expr::Unit(pos))
}
// lhs.rhs
(lhs, rhs) => Expr::Dot(
Box::new(optimize_expr(lhs, state)),
Box::new(optimize_expr(rhs, state)),
pos,
)
(lhs, rhs) => Expr::Dot(Box::new((optimize_expr(lhs, state), optimize_expr(rhs, state), x.2)))
}
// lhs[rhs]
#[cfg(not(feature = "no_index"))]
Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) {
Expr::Index(x) => match (x.0, x.1) {
// array[int]
(Expr::Array(mut items, pos), Expr::IntegerConstant(i, _))
if i >= 0 && (i as usize) < items.len() && items.iter().all(Expr::is_pure) =>
(Expr::Array(mut a), Expr::IntegerConstant(i))
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.
// All other items can be thrown away.
state.set_dirty();
items.remove(i as usize).set_position(pos)
a.0.remove(i.0 as usize).set_position(a.1)
}
// 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.
// All other items can be thrown away.
state.set_dirty();
items.into_iter().find(|(name, _, _)| name == &s)
.map(|(_, expr, _)| expr.set_position(pos))
let pos = m.1;
m.0.into_iter().find(|((name, _), _)| name == &s.0)
.map(|(_, expr)| expr.set_position(pos))
.unwrap_or_else(|| Expr::Unit(pos))
}
// 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
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) => Expr::Index(
Box::new(optimize_expr(lhs, state)),
Box::new(optimize_expr(rhs, state)),
pos,
),
(lhs, rhs) => Expr::Index(Box::new((optimize_expr(lhs, state), optimize_expr(rhs, state), x.2))),
},
// [ items .. ]
#[cfg(not(feature = "no_index"))]
Expr::Array(items, pos) => Expr::Array(items
.into_iter()
.map(|expr| optimize_expr(expr, state))
.collect(), pos),
Expr::Array(a) => Expr::Array(Box::new((a.0
.into_iter()
.map(|expr| optimize_expr(expr, state))
.collect(), a.1))),
// [ items .. ]
#[cfg(not(feature = "no_object"))]
Expr::Map(items, pos) => Expr::Map(items
.into_iter()
.map(|(key, expr, pos)| (key, optimize_expr(expr, state), pos))
.collect(), pos),
Expr::Map(m) => Expr::Map(Box::new((m.0
.into_iter()
.map(|((key, pos), expr)| ((key, pos), optimize_expr(expr, state)))
.collect(), m.1))),
// lhs in rhs
Expr::In(lhs, rhs, pos) => match (*lhs, *rhs) {
Expr::In(x) => match (x.0, x.1) {
// "xxx" in "xxxxx"
(Expr::StringConstant(lhs, pos), Expr::StringConstant(rhs, _)) => {
(Expr::StringConstant(a), Expr::StringConstant(b)) => {
state.set_dirty();
if rhs.contains(&lhs) {
Expr::True(pos)
} else {
Expr::False(pos)
}
if b.0.contains(&a.0) { Expr::True(a.1) } else { Expr::False(a.1) }
}
// 'x' in "xxxxx"
(Expr::CharConstant(lhs, pos), Expr::StringConstant(rhs, _)) => {
(Expr::CharConstant(a), Expr::StringConstant(b)) => {
state.set_dirty();
if rhs.contains(&lhs.to_string()) {
Expr::True(pos)
} else {
Expr::False(pos)
}
if b.0.contains(a.0) { Expr::True(a.1) } else { Expr::False(a.1) }
}
// "xxx" in #{...}
(Expr::StringConstant(lhs, pos), Expr::Map(items, _)) => {
(Expr::StringConstant(a), Expr::Map(b)) => {
state.set_dirty();
if items.iter().find(|(name, _, _)| name == &lhs).is_some() {
Expr::True(pos)
if b.0.iter().find(|((name, _), _)| name == &a.0).is_some() {
Expr::True(a.1)
} else {
Expr::False(pos)
Expr::False(a.1)
}
}
// 'x' in #{...}
(Expr::CharConstant(lhs, pos), Expr::Map(items, _)) => {
(Expr::CharConstant(a), Expr::Map(b)) => {
state.set_dirty();
let lhs = lhs.to_string();
let ch = a.0.to_string();
if items.iter().find(|(name, _, _)| name == &lhs).is_some() {
Expr::True(pos)
if b.0.iter().find(|((name, _), _)| name == &ch).is_some() {
Expr::True(a.1)
} else {
Expr::False(pos)
Expr::False(a.1)
}
}
// lhs in rhs
(lhs, rhs) => Expr::In(
Box::new(optimize_expr(lhs, state)),
Box::new(optimize_expr(rhs, state)),
pos
),
(lhs, rhs) => Expr::In(Box::new((optimize_expr(lhs, state), optimize_expr(rhs, state), x.2))),
},
// lhs && rhs
Expr::And(lhs, rhs, pos) => match (*lhs, *rhs) {
Expr::And(x) => match (x.0, x.1) {
// true && rhs -> rhs
(Expr::True(_), rhs) => {
state.set_dirty();
@ -524,14 +503,10 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
optimize_expr(lhs, state)
}
// lhs && rhs
(lhs, rhs) => Expr::And(
Box::new(optimize_expr(lhs, state)),
Box::new(optimize_expr(rhs, state)),
pos
),
(lhs, rhs) => Expr::And(Box::new((optimize_expr(lhs, state), optimize_expr(rhs, state), x.2))),
},
// lhs || rhs
Expr::Or(lhs, rhs, pos) => match (*lhs, *rhs) {
Expr::Or(x) => match (x.0, x.1) {
// false || rhs -> rhs
(Expr::False(_), rhs) => {
state.set_dirty();
@ -548,22 +523,28 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
optimize_expr(lhs, state)
}
// 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
Expr::FnCall(id, None, args, def_value, pos) if DONT_EVAL_KEYWORDS.contains(&id.as_ref().as_ref())=>
Expr::FnCall(id, None, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos),
Expr::FnCall(mut x) if DONT_EVAL_KEYWORDS.contains(&(x.0).0.as_ref())=> {
x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
Expr::FnCall(x)
}
// Eagerly call functions
Expr::FnCall(id, None, args, def_value, pos)
if state.optimization_level == OptimizationLevel::Full // full optimizations
&& args.iter().all(|expr| expr.is_constant()) // all arguments are constants
Expr::FnCall(mut x)
if x.1.is_none() // Non-qualified
&& 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)
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
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();
@ -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()`
// 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())
} 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|
result.or_else(|| {
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())
} else {
// 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| {
state.set_dirty();
expr
})
).unwrap_or_else(||
).unwrap_or_else(|| {
// 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
Expr::FnCall(id, modules, args, def_value, pos) =>
Expr::FnCall(id, modules, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos),
Expr::FnCall(mut x) => {
x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
Expr::FnCall(x)
}
// 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();
// Replace constant with value
@ -660,17 +645,18 @@ fn optimize<'a>(
.into_iter()
.enumerate()
.map(|(i, stmt)| {
match stmt {
Stmt::Const(ref name, ref value, _) => {
match &stmt {
Stmt::Const(v) => {
// 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
}
_ => {
// Keep all variable declarations at this level
// and always keep the last return value
let keep = match stmt {
Stmt::Let(_, _, _) | Stmt::Import(_, _, _) => true,
Stmt::Let(_) | Stmt::Import(_) => true,
_ => i == num_statements - 1,
};
optimize_stmt(stmt, &mut state, keep)
@ -721,34 +707,29 @@ pub fn optimize_into_ast(
const fn_lib: &[(&str, usize)] = &[];
#[cfg(not(feature = "no_function"))]
let lib = FunctionsLib::from_vec(
functions
.iter()
.cloned()
.map(|mut fn_def| {
if !level.is_none() {
let pos = fn_def.body.position();
let lib = FunctionsLib::from_iter(functions.iter().cloned().map(|mut fn_def| {
if !level.is_none() {
let pos = fn_def.body.position();
// Optimize the function body
let mut body =
optimize(vec![*fn_def.body], engine, &Scope::new(), &fn_lib, level);
// Optimize the function body
let mut body = optimize(vec![fn_def.body], engine, &Scope::new(), &fn_lib, level);
// {} -> Noop
fn_def.body = Box::new(match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
// { return val; } -> val
Stmt::ReturnWithVal(Some(val), ReturnType::Return, _) => Stmt::Expr(val),
// { return; } -> ()
Stmt::ReturnWithVal(None, ReturnType::Return, pos) => {
Stmt::Expr(Box::new(Expr::Unit(pos)))
}
// All others
stmt => stmt,
});
// {} -> Noop
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
// { return val; } -> val
Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => {
Stmt::Expr(Box::new(x.1.unwrap()))
}
fn_def
})
.collect(),
);
// { return; } -> ()
Stmt::ReturnWithVal(x) if x.1.is_none() && (x.0).0 == ReturnType::Return => {
Stmt::Expr(Box::new(Expr::Unit((x.0).1)))
}
// All others
stmt => stmt,
};
}
fn_def
}));
#[cfg(feature = "no_function")]
let lib: FunctionsLib = Default::default();

View File

@ -1,7 +1,5 @@
use super::{reg_binary, reg_unary};
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::result::EvalAltResult;
use crate::token::Position;
@ -22,7 +20,7 @@ use crate::stdlib::{
};
// 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(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Addition overflow: {} + {}", x, y),
@ -31,7 +29,7 @@ fn add<T: Display + CheckedAdd>(x: T, y: T) -> Result<T, Box<EvalAltResult>> {
})
}
// 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(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Subtraction underflow: {} - {}", x, y),
@ -40,7 +38,7 @@ fn sub<T: Display + CheckedSub>(x: T, y: T) -> Result<T, Box<EvalAltResult>> {
})
}
// 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(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Multiplication overflow: {} * {}", x, y),
@ -49,7 +47,7 @@ fn mul<T: Display + CheckedMul>(x: T, y: T) -> Result<T, Box<EvalAltResult>> {
})
}
// Checked divide
fn div<T>(x: T, y: T) -> Result<T, Box<EvalAltResult>>
fn div<T>(x: T, y: T) -> FuncReturn<T>
where
T: Display + CheckedDiv + PartialEq + Zero,
{
@ -69,7 +67,7 @@ where
})
}
// 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(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Negation overflow: -{}", x),
@ -78,7 +76,7 @@ fn neg<T: Display + CheckedNeg>(x: T) -> Result<T, Box<EvalAltResult>> {
})
}
// 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
// when the number is ::MIN instead of returning ::MIN itself.
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
fn add_u<T: Add>(x: T, y: T) -> <T as Add>::Output {
x + y
fn add_u<T: Add>(x: T, y: T) -> FuncReturn<<T as Add>::Output> {
Ok(x + y)
}
// Unchecked subtract - may panic on underflow
fn sub_u<T: Sub>(x: T, y: T) -> <T as Sub>::Output {
x - y
fn sub_u<T: Sub>(x: T, y: T) -> FuncReturn<<T as Sub>::Output> {
Ok(x - y)
}
// Unchecked multiply - may panic on overflow
fn mul_u<T: Mul>(x: T, y: T) -> <T as Mul>::Output {
x * y
fn mul_u<T: Mul>(x: T, y: T) -> FuncReturn<<T as Mul>::Output> {
Ok(x * y)
}
// Unchecked divide - may panic when dividing by zero
fn div_u<T: Div>(x: T, y: T) -> <T as Div>::Output {
x / y
fn div_u<T: Div>(x: T, y: T) -> FuncReturn<<T as Div>::Output> {
Ok(x / y)
}
// Unchecked negative - may panic on overflow
fn neg_u<T: Neg>(x: T) -> <T as Neg>::Output {
-x
fn neg_u<T: Neg>(x: T) -> FuncReturn<<T as Neg>::Output> {
Ok(-x)
}
// 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
T: Neg + PartialOrd + Default + Into<<T as Neg>::Output>,
{
// Numbers should default to zero
if x < Default::default() {
-x
Ok(-x)
} else {
x.into()
Ok(x.into())
}
}
// Bit operators
fn binary_and<T: BitAnd>(x: T, y: T) -> <T as BitAnd>::Output {
x & y
fn binary_and<T: BitAnd>(x: T, y: T) -> FuncReturn<<T as BitAnd>::Output> {
Ok(x & y)
}
fn binary_or<T: BitOr>(x: T, y: T) -> <T as BitOr>::Output {
x | y
fn binary_or<T: BitOr>(x: T, y: T) -> FuncReturn<<T as BitOr>::Output> {
Ok(x | y)
}
fn binary_xor<T: BitXor>(x: T, y: T) -> <T as BitXor>::Output {
x ^ y
fn binary_xor<T: BitXor>(x: T, y: T) -> FuncReturn<<T as BitXor>::Output> {
Ok(x ^ y)
}
// 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
if y < 0 {
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
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
if y < 0 {
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
fn shl_u<T: Shl<T>>(x: T, y: T) -> <T as Shl<T>>::Output {
x.shl(y)
fn shl_u<T: Shl<T>>(x: T, y: T) -> FuncReturn<<T as Shl<T>>::Output> {
Ok(x.shl(y))
}
// 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 {
x.shr(y)
fn shr_u<T: Shr<T>>(x: T, y: T) -> FuncReturn<<T as Shr<T>>::Output> {
Ok(x.shr(y))
}
// 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(|| {
Box::new(EvalAltResult::ErrorArithmetic(
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
fn modulo_u<T: Rem>(x: T, y: T) -> <T as Rem>::Output {
x % y
fn modulo_u<T: Rem>(x: T, y: T) -> FuncReturn<<T as Rem>::Output> {
Ok(x % y)
}
// 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"))]
{
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)
fn pow_i_i_u(x: INT, y: INT) -> INT {
x.pow(y as u32)
fn pow_i_i_u(x: INT, y: INT) -> FuncReturn<INT> {
Ok(x.pow(y as u32))
}
// Floating-point power - always well-defined
#[cfg(not(feature = "no_float"))]
fn pow_f_f(x: FLOAT, y: FLOAT) -> FLOAT {
x.powf(y)
fn pow_f_f(x: FLOAT, y: FLOAT) -> FuncReturn<FLOAT> {
Ok(x.powf(y))
}
// Checked power
#[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
if y > (i32::MAX as INT) {
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)
#[cfg(feature = "unchecked")]
#[cfg(not(feature = "no_float"))]
fn pow_f_i_u(x: FLOAT, y: INT) -> FLOAT {
x.powi(y as i32)
fn pow_f_i_u(x: FLOAT, y: INT) -> FuncReturn<FLOAT> {
Ok(x.powi(y as i32))
}
macro_rules! reg_unary_x { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
$(reg_unary($lib, $op, $func::<$par>, result);)* };
macro_rules! reg_unary {
($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),*) => {
$(reg_unary($lib, $op, $func::<$par>, map);)* };
}
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);)* };
macro_rules! reg_op {
($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
$( $lib.set_fn_2($op, $func::<$par>); )*
};
}
def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
// Checked basic arithmetic
#[cfg(not(feature = "unchecked"))]
{
reg_op_x!(lib, "+", add, INT);
reg_op_x!(lib, "-", sub, INT);
reg_op_x!(lib, "*", mul, INT);
reg_op_x!(lib, "/", div, INT);
reg_op!(lib, "+", add, INT);
reg_op!(lib, "-", sub, INT);
reg_op!(lib, "*", mul, INT);
reg_op!(lib, "/", div, INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_op_x!(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_x!(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, "+", add, 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!(lib, "*", mul, 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
#[cfg(not(feature = "unchecked"))]
{
reg_op_x!(lib, "<<", shl, INT);
reg_op_x!(lib, ">>", shr, INT);
reg_op_x!(lib, "%", modulo, INT);
reg_op!(lib, "<<", shl, INT);
reg_op!(lib, ">>", shr, INT);
reg_op!(lib, "%", modulo, INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_op_x!(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_x!(lib, "%", modulo, 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!(lib, ">>", shr, 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
#[cfg(not(feature = "unchecked"))]
{
reg_binary(lib, "~", pow_i_i, result);
lib.set_fn_2("~", pow_i_i);
#[cfg(not(feature = "no_float"))]
reg_binary(lib, "~", pow_f_i, result);
lib.set_fn_2("~", pow_f_i);
}
// Unchecked power
#[cfg(feature = "unchecked")]
{
reg_binary(lib, "~", pow_i_i_u, map);
lib.set_fn_2("~", pow_i_i_u);
#[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
#[cfg(not(feature = "no_float"))]
{
reg_op!(lib, "%", modulo_u, f32, f64);
reg_binary(lib, "~", pow_f_f, map);
lib.set_fn_2("~", pow_f_f);
}
// Checked unary
#[cfg(not(feature = "unchecked"))]
{
reg_unary_x!(lib, "-", neg, INT);
reg_unary_x!(lib, "abs", abs, INT);
reg_unary!(lib, "-", neg, INT);
reg_unary!(lib, "abs", abs, INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_unary_x!(lib, "-", neg, i8, i16, i32, i64, i128);
reg_unary_x!(lib, "abs", abs, i8, i16, i32, i64, i128);
reg_unary!(lib, "-", neg, 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"))]
use super::{reg_binary, reg_binary_mut, reg_trinary_mut, reg_unary_mut};
use crate::any::{Dynamic, Variant};
use crate::def_package;
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::stdlib::{any::TypeId, boxed::Box, string::String};
// 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));
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 {
list.insert(0, Dynamic::from(item));
} else if (position as usize) >= list.len() - 1 {
push(list, item);
push(list, item)?;
} else {
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 {
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),*) => {
$(reg_binary_mut($lib, $op, $func::<$par>, map);)* };
macro_rules! reg_op {
($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),*) => {
$(reg_trinary_mut($lib, $op, $func::<$par>, map);)* };
macro_rules! reg_tri {
($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
$( $lib.set_fn_3_mut($op, $func::<$par>); )*
};
}
#[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, "insert", ins, INT, bool, char, String, Array, ());
reg_binary_mut(lib, "append", |x: &mut Array, y: Array| x.extend(y), map);
reg_binary(
lib,
lib.set_fn_2_mut("append", |x: &mut Array, y: Array| {
x.extend(y);
Ok(())
});
lib.set_fn_2(
"+",
|mut x: Array, y: Array| {
x.extend(y);
x
Ok(x)
},
map,
);
#[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_unary_mut(
lib,
lib.set_fn_1_mut(
"pop",
|list: &mut Array| list.pop().unwrap_or_else(|| ().into()),
pass,
|list: &mut Array| Ok(list.pop().unwrap_or_else(|| ().into())),
);
reg_unary_mut(
lib,
lib.set_fn_1_mut(
"shift",
|list: &mut Array| {
if list.is_empty() {
Ok(if list.is_empty() {
().into()
} else {
list.remove(0)
}
})
},
pass,
);
reg_binary_mut(
lib,
lib.set_fn_2_mut(
"remove",
|list: &mut Array, len: INT| {
if len < 0 || (len as usize) >= list.len() {
Ok(if len < 0 || (len as usize) >= list.len() {
().into()
} else {
list.remove(len as usize)
}
})
},
pass,
);
reg_unary_mut(lib, "len", |list: &mut Array| list.len() as INT, map);
reg_unary_mut(lib, "clear", |list: &mut Array| list.clear(), map);
reg_binary_mut(
lib,
lib.set_fn_1_mut("len", |list: &mut Array| Ok(list.len() as INT));
lib.set_fn_1_mut("clear", |list: &mut Array| {
list.clear();
Ok(())
});
lib.set_fn_2_mut(
"truncate",
|list: &mut Array, len: INT| {
if len >= 0 {
@ -111,16 +113,15 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
} else {
list.clear();
}
Ok(())
},
map,
);
// Register array iterator
lib.type_iterators.insert(
lib.set_iter(
TypeId::of::<Array>(),
Box::new(|a: Dynamic| {
Box::new(a.cast::<Array>().into_iter())
as Box<dyn Iterator<Item = Dynamic>>
}),
Box::new(|arr| Box::new(
arr.cast::<Array>().into_iter()) 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::def_package;
use crate::fn_register::map_dynamic as map;
use crate::module::{FuncReturn, Module};
use crate::parser::INT;
use crate::stdlib::{
@ -12,19 +10,23 @@ use crate::stdlib::{
};
// Register range function
fn reg_range<T: Variant + Clone>(lib: &mut PackageStore)
fn reg_range<T: Variant + Clone>(lib: &mut Module)
where
Range<T>: Iterator<Item = T>,
{
lib.type_iterators.insert(
lib.set_iter(
TypeId::of::<Range<T>>(),
Box::new(|source: Dynamic| {
Box::new(|source| {
Box::new(source.cast::<Range<T>>().map(|x| x.into_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
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
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
for<'a> &'a T: Add<&'a T, Output = T>,
T: Variant + Clone + PartialOrd,
StepRange<T>: Iterator<Item = T>,
{
lib.type_iterators.insert(
lib.set_iter(
TypeId::of::<StepRange<T>>(),
Box::new(|source: Dynamic| {
Box::new(|source| {
Box::new(source.cast::<StepRange<T>>().map(|x| x.into_dynamic()))
as Box<dyn Iterator<Item = Dynamic>>
}),
);
}
def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
fn get_range<T>(from: T, to: T) -> Range<T> {
from..to
}
fn get_step_range<T>(from: T, to: T, step: T) -> FuncReturn<StepRange<T>>
where
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_binary(lib, "range", get_range::<INT>, map);
lib.set_fn_2("range", get_range::<INT>);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
macro_rules! reg_range {
($self:expr, $x:expr, $( $y:ty ),*) => (
($lib:expr, $x:expr, $( $y:ty ),*) => (
$(
reg_range::<$y>($self);
reg_binary($self, $x, get_range::<$y>, map);
reg_range::<$y>($lib);
$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_trinary(lib, "range", StepRange::<INT>, map);
lib.set_fn_3("range", get_step_range::<INT>);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
macro_rules! reg_step {
($self:expr, $x:expr, $( $y:ty ),*) => (
($lib:expr, $x:expr, $( $y:ty ),*) => (
$(
reg_step::<$y>($self);
reg_trinary($self, $x, StepRange::<$y>, map);
reg_step::<$y>($lib);
$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::fn_register::map_dynamic as map;
use crate::module::FuncReturn;
use crate::parser::INT;
use crate::stdlib::string::String;
// Comparison operators
pub fn lt<T: PartialOrd>(x: T, y: T) -> bool {
x < y
pub fn lt<T: PartialOrd>(x: T, y: T) -> FuncReturn<bool> {
Ok(x < y)
}
pub fn lte<T: PartialOrd>(x: T, y: T) -> bool {
x <= y
pub fn lte<T: PartialOrd>(x: T, y: T) -> FuncReturn<bool> {
Ok(x <= y)
}
pub fn gt<T: PartialOrd>(x: T, y: T) -> bool {
x > y
pub fn gt<T: PartialOrd>(x: T, y: T) -> FuncReturn<bool> {
Ok(x > y)
}
pub fn gte<T: PartialOrd>(x: T, y: T) -> bool {
x >= y
pub fn gte<T: PartialOrd>(x: T, y: T) -> FuncReturn<bool> {
Ok(x >= y)
}
pub fn eq<T: PartialEq>(x: T, y: T) -> bool {
x == y
pub fn eq<T: PartialEq>(x: T, y: T) -> FuncReturn<bool> {
Ok(x == y)
}
pub fn ne<T: PartialEq>(x: T, y: T) -> bool {
x != y
pub fn ne<T: PartialEq>(x: T, y: T) -> FuncReturn<bool> {
Ok(x != y)
}
// Logic operators
fn and(x: bool, y: bool) -> bool {
x && y
fn and(x: bool, y: bool) -> FuncReturn<bool> {
Ok(x && y)
}
fn or(x: bool, y: bool) -> bool {
x || y
fn or(x: bool, y: bool) -> FuncReturn<bool> {
Ok(x || y)
}
fn not(x: bool) -> bool {
!x
fn not(x: bool) -> FuncReturn<bool> {
Ok(!x)
}
macro_rules! reg_op { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
$(reg_binary($lib, $op, $func::<$par>, map);)* };
macro_rules! reg_op {
($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
$( $lib.set_fn_2($op, $func::<$par>); )*
};
}
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, ());
// Special versions for strings - at least avoid copying the first string
// use super::utils::reg_test;
// reg_test(lib, "<", |x: &mut String, y: String| *x < y, |v| v, map);
reg_binary_mut(lib, "<", |x: &mut String, y: String| *x < y, map);
reg_binary_mut(lib, "<=", |x: &mut String, y: String| *x <= y, map);
reg_binary_mut(lib, ">", |x: &mut String, y: String| *x > y, map);
reg_binary_mut(lib, ">=", |x: &mut String, y: String| *x >= y, map);
reg_binary_mut(lib, "==", |x: &mut String, y: String| *x == y, map);
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));
lib.set_fn_2_mut("<=", |x: &mut String, y: String| Ok(*x <= y));
lib.set_fn_2_mut(">", |x: &mut String, y: String| Ok(*x > y));
lib.set_fn_2_mut(">=", |x: &mut String, y: String| Ok(*x >= y));
lib.set_fn_2_mut("==", |x: &mut String, y: String| Ok(*x == y));
lib.set_fn_2_mut("!=", |x: &mut String, y: String| Ok(*x != y));
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
@ -85,7 +83,7 @@ def_package!(crate:LogicPackage:"Logical operators.", lib, {
//reg_op!(lib, "||", or, bool);
//reg_op!(lib, "&&", and, bool);
reg_binary(lib, "|", or, map);
reg_binary(lib, "&", and, map);
reg_unary(lib, "!", not, map);
lib.set_fn_2("|", or);
lib.set_fn_2("&", and);
lib.set_fn_1("!", not);
});

View File

@ -1,11 +1,9 @@
#![cfg(not(feature = "no_object"))]
use super::{reg_binary, reg_binary_mut, reg_unary_mut};
use crate::any::Dynamic;
use crate::def_package;
use crate::engine::Map;
use crate::fn_register::map_dynamic as map;
use crate::module::FuncReturn;
use crate::parser::INT;
use crate::stdlib::{
@ -13,55 +11,51 @@ use crate::stdlib::{
vec::Vec,
};
fn map_get_keys(map: &mut Map) -> Vec<Dynamic> {
map.iter().map(|(k, _)| k.to_string().into()).collect()
fn map_get_keys(map: &mut Map) -> FuncReturn<Vec<Dynamic>> {
Ok(map.iter().map(|(k, _)| k.to_string().into()).collect())
}
fn map_get_values(map: &mut Map) -> Vec<Dynamic> {
map.iter().map(|(_, v)| v.clone()).collect()
fn map_get_values(map: &mut Map) -> FuncReturn<Vec<Dynamic>> {
Ok(map.iter().map(|(_, v)| v.clone()).collect())
}
#[cfg(not(feature = "no_object"))]
def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
reg_binary_mut(
lib,
lib.set_fn_2_mut(
"has",
|map: &mut Map, prop: String| map.contains_key(&prop),
map,
|map: &mut Map, prop: String| Ok(map.contains_key(&prop)),
);
reg_unary_mut(lib, "len", |map: &mut Map| map.len() as INT, map);
reg_unary_mut(lib, "clear", |map: &mut Map| map.clear(), map);
reg_binary_mut(
lib,
lib.set_fn_1_mut("len", |map: &mut Map| Ok(map.len() as INT));
lib.set_fn_1_mut("clear", |map: &mut Map| {
map.clear();
Ok(())
});
lib.set_fn_2_mut(
"remove",
|x: &mut Map, name: String| x.remove(&name).unwrap_or_else(|| ().into()),
map,
|x: &mut Map, name: String| Ok(x.remove(&name).unwrap_or_else(|| ().into())),
);
reg_binary_mut(
lib,
lib.set_fn_2_mut(
"mixin",
|map1: &mut Map, map2: Map| {
map2.into_iter().for_each(|(key, value)| {
map1.insert(key, value);
});
Ok(())
},
map,
);
reg_binary(
lib,
lib.set_fn_2(
"+",
|mut map1: Map, map2: Map| {
map2.into_iter().for_each(|(key, value)| {
map1.insert(key, value);
});
map1
Ok(map1)
},
map,
);
// Register map access functions
#[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"))]
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::fn_register::{map_dynamic as map, map_result as result};
use crate::parser::INT;
use crate::result::EvalAltResult;
use crate::token::Position;
@ -20,78 +17,77 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
#[cfg(not(feature = "no_float"))]
{
// Advanced math functions
reg_unary(lib, "sin", |x: FLOAT| x.to_radians().sin(), map);
reg_unary(lib, "cos", |x: FLOAT| x.to_radians().cos(), map);
reg_unary(lib, "tan", |x: FLOAT| x.to_radians().tan(), map);
reg_unary(lib, "sinh", |x: FLOAT| x.to_radians().sinh(), map);
reg_unary(lib, "cosh", |x: FLOAT| x.to_radians().cosh(), map);
reg_unary(lib, "tanh", |x: FLOAT| x.to_radians().tanh(), map);
reg_unary(lib, "asin", |x: FLOAT| x.asin().to_degrees(), map);
reg_unary(lib, "acos", |x: FLOAT| x.acos().to_degrees(), map);
reg_unary(lib, "atan", |x: FLOAT| x.atan().to_degrees(), map);
reg_unary(lib, "asinh", |x: FLOAT| x.asinh().to_degrees(), map);
reg_unary(lib, "acosh", |x: FLOAT| x.acosh().to_degrees(), map);
reg_unary(lib, "atanh", |x: FLOAT| x.atanh().to_degrees(), map);
reg_unary(lib, "sqrt", |x: FLOAT| x.sqrt(), map);
reg_unary(lib, "exp", |x: FLOAT| x.exp(), map);
reg_unary(lib, "ln", |x: FLOAT| x.ln(), map);
reg_binary(lib, "log", |x: FLOAT, base: FLOAT| x.log(base), map);
reg_unary(lib, "log10", |x: FLOAT| x.log10(), map);
reg_unary(lib, "floor", |x: FLOAT| x.floor(), map);
reg_unary(lib, "ceiling", |x: FLOAT| x.ceil(), map);
reg_unary(lib, "round", |x: FLOAT| x.ceil(), map);
reg_unary(lib, "int", |x: FLOAT| x.trunc(), map);
reg_unary(lib, "fraction", |x: FLOAT| x.fract(), map);
reg_unary(lib, "is_nan", |x: FLOAT| x.is_nan(), map);
reg_unary(lib, "is_finite", |x: FLOAT| x.is_finite(), map);
reg_unary(lib, "is_infinite", |x: FLOAT| x.is_infinite(), map);
lib.set_fn_1("sin", |x: FLOAT| Ok(x.to_radians().sin()));
lib.set_fn_1("cos", |x: FLOAT| Ok(x.to_radians().cos()));
lib.set_fn_1("tan", |x: FLOAT| Ok(x.to_radians().tan()));
lib.set_fn_1("sinh", |x: FLOAT| Ok(x.to_radians().sinh()));
lib.set_fn_1("cosh", |x: FLOAT| Ok(x.to_radians().cosh()));
lib.set_fn_1("tanh", |x: FLOAT| Ok(x.to_radians().tanh()));
lib.set_fn_1("asin", |x: FLOAT| Ok(x.asin().to_degrees()));
lib.set_fn_1("acos", |x: FLOAT| Ok(x.acos().to_degrees()));
lib.set_fn_1("atan", |x: FLOAT| Ok(x.atan().to_degrees()));
lib.set_fn_1("asinh", |x: FLOAT| Ok(x.asinh().to_degrees()));
lib.set_fn_1("acosh", |x: FLOAT| Ok(x.acosh().to_degrees()));
lib.set_fn_1("atanh", |x: FLOAT| Ok(x.atanh().to_degrees()));
lib.set_fn_1("sqrt", |x: FLOAT| Ok(x.sqrt()));
lib.set_fn_1("exp", |x: FLOAT| Ok(x.exp()));
lib.set_fn_1("ln", |x: FLOAT| Ok(x.ln()));
lib.set_fn_2("log", |x: FLOAT, base: FLOAT| Ok(x.log(base)));
lib.set_fn_1("log10", |x: FLOAT| Ok(x.log10()));
lib.set_fn_1("floor", |x: FLOAT| Ok(x.floor()));
lib.set_fn_1("ceiling", |x: FLOAT| Ok(x.ceil()));
lib.set_fn_1("round", |x: FLOAT| Ok(x.ceil()));
lib.set_fn_1("int", |x: FLOAT| Ok(x.trunc()));
lib.set_fn_1("fraction", |x: FLOAT| Ok(x.fract()));
lib.set_fn_1("is_nan", |x: FLOAT| Ok(x.is_nan()));
lib.set_fn_1("is_finite", |x: FLOAT| Ok(x.is_finite()));
lib.set_fn_1("is_infinite", |x: FLOAT| Ok(x.is_infinite()));
// Register conversion functions
reg_unary(lib, "to_float", |x: INT| x as FLOAT, map);
reg_unary(lib, "to_float", |x: f32| x as FLOAT, map);
lib.set_fn_1("to_float", |x: INT| Ok(x as FLOAT));
lib.set_fn_1("to_float", |x: f32| Ok(x as FLOAT));
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_unary(lib, "to_float", |x: i8| x as FLOAT, map);
reg_unary(lib, "to_float", |x: u8| x as FLOAT, map);
reg_unary(lib, "to_float", |x: i16| x as FLOAT, map);
reg_unary(lib, "to_float", |x: u16| x as FLOAT, map);
reg_unary(lib, "to_float", |x: i32| x as FLOAT, map);
reg_unary(lib, "to_float", |x: u32| x as FLOAT, map);
reg_unary(lib, "to_float", |x: i64| x as FLOAT, map);
reg_unary(lib, "to_float", |x: u64| x as FLOAT, map);
reg_unary(lib, "to_float", |x: i128| x as FLOAT, map);
reg_unary(lib, "to_float", |x: u128| x as FLOAT, map);
lib.set_fn_1("to_float", |x: i8| Ok(x as FLOAT));
lib.set_fn_1("to_float", |x: u8| Ok(x as FLOAT));
lib.set_fn_1("to_float", |x: i16| Ok(x as FLOAT));
lib.set_fn_1("to_float", |x: u16| Ok(x as FLOAT));
lib.set_fn_1("to_float", |x: i32| Ok(x as FLOAT));
lib.set_fn_1("to_float", |x: u32| Ok(x as FLOAT));
lib.set_fn_1("to_float", |x: i64| Ok(x as FLOAT));
lib.set_fn_1("to_float", |x: u64| Ok(x as FLOAT));
lib.set_fn_1("to_float", |x: i128| Ok(x as FLOAT));
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_i64"))]
{
reg_unary(lib, "to_int", |x: i8| x as INT, map);
reg_unary(lib, "to_int", |x: u8| x as INT, map);
reg_unary(lib, "to_int", |x: i16| x as INT, map);
reg_unary(lib, "to_int", |x: u16| x as INT, map);
lib.set_fn_1("to_int", |x: i8| Ok(x as INT));
lib.set_fn_1("to_int", |x: u8| Ok(x as INT));
lib.set_fn_1("to_int", |x: i16| Ok(x as INT));
lib.set_fn_1("to_int", |x: u16| Ok(x as INT));
}
#[cfg(not(feature = "only_i32"))]
{
reg_unary(lib, "to_int", |x: i32| x as INT, map);
reg_unary(lib, "to_int", |x: u64| x as INT, map);
lib.set_fn_1("to_int", |x: i32| Ok(x as INT));
lib.set_fn_1("to_int", |x: u64| Ok(x as INT));
#[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 = "unchecked"))]
{
reg_unary(
lib,
lib.set_fn_1(
"to_int",
|x: f32| {
if x > (MAX_INT as f32) {
@ -103,10 +99,8 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
Ok(x.trunc() as INT)
},
result,
);
reg_unary(
lib,
lib.set_fn_1(
"to_int",
|x: FLOAT| {
if x > (MAX_INT as FLOAT) {
@ -118,14 +112,13 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
Ok(x.trunc() as INT)
},
result,
);
}
#[cfg(feature = "unchecked")]
{
reg_unary(lib, "to_int", |x: f32| x as INT, map);
reg_unary(lib, "to_int", |x: f64| x as INT, map);
lib.set_fn_1("to_int", |x: f32| Ok(x as INT));
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.
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 array_basic;
@ -15,7 +16,6 @@ mod pkg_std;
mod string_basic;
mod string_more;
mod time_basic;
mod utils;
pub use arithmetic::ArithmeticPackage;
#[cfg(not(feature = "no_index"))]
@ -32,41 +32,107 @@ pub use string_more::MoreStringPackage;
#[cfg(not(feature = "no_std"))]
pub use time_basic::BasicTimePackage;
pub use utils::*;
/// Trait that all packages must implement.
pub trait Package {
/// Create a new instance of a package.
fn new() -> Self;
/// 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.
fn get(&self) -> PackageLibrary;
}
/// Type to store all functions in the package.
#[derive(Default)]
pub struct PackageStore {
/// All functions, keyed by a hash created from the function name and parameter types.
pub functions: HashMap<u64, Box<FnAny>>,
/// Type which `Rc`-wraps a `Module` to facilitate sharing library instances.
#[cfg(not(feature = "sync"))]
pub type PackageLibrary = Rc<Module>;
/// All iterator functions, keyed by the type producing the iterator.
pub type_iterators: HashMap<TypeId, Box<IteratorFn>>,
/// Type which `Arc`-wraps a `Module` to facilitate sharing library instances.
#[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 {
/// Create a new `PackageStore`.
pub fn new() -> Self {
Default::default()
impl PackagesCollection {
/// Add a `PackageLibrary` into the `PackagesCollection`.
pub fn push(&mut self, package: PackageLibrary) {
// 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.
#[cfg(not(feature = "sync"))]
pub type PackageLibrary = Rc<PackageStore>;
/// This macro makes it easy to define a _package_ (which is basically a shared module)
/// and register functions into it.
///
/// 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.
#[cfg(feature = "sync")]
pub type PackageLibrary = Arc<PackageStore>;
impl $root::packages::Package for $package {
fn get(&self) -> $root::packages::PackageLibrary {
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::engine::{FUNC_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT};
use crate::fn_register::map_dynamic as map;
use crate::module::FuncReturn;
use crate::parser::INT;
#[cfg(not(feature = "no_index"))]
@ -18,31 +16,33 @@ use crate::stdlib::{
};
// Register print and debug
fn to_debug<T: Debug>(x: &mut T) -> String {
format!("{:?}", x)
fn to_debug<T: Debug>(x: &mut T) -> FuncReturn<String> {
Ok(format!("{:?}", x))
}
fn to_string<T: Display>(x: &mut T) -> String {
format!("{}", x)
fn to_string<T: Display>(x: &mut T) -> FuncReturn<String> {
Ok(format!("{}", x))
}
#[cfg(not(feature = "no_object"))]
fn format_map(x: &mut Map) -> String {
format!("#{:?}", x)
fn format_map(x: &mut Map) -> FuncReturn<String> {
Ok(format!("#{:?}", x))
}
macro_rules! reg_op { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
$(reg_unary_mut($lib, $op, $func::<$par>, map);)* };
macro_rules! reg_op {
($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, {
reg_op!(lib, KEYWORD_PRINT, to_string, INT, bool, char);
reg_op!(lib, FUNC_TO_STRING, to_string, INT, bool, char);
reg_none(lib, KEYWORD_PRINT, || "".to_string(), map);
reg_unary(lib, KEYWORD_PRINT, |_: ()| "".to_string(), map);
reg_unary(lib, FUNC_TO_STRING, |_: ()| "".to_string(), map);
lib.set_fn_0(KEYWORD_PRINT, || Ok("".to_string()));
lib.set_fn_1(KEYWORD_PRINT, |_: ()| Ok("".to_string()));
lib.set_fn_1(FUNC_TO_STRING, |_: ()| Ok("".to_string()));
reg_unary_mut(lib, KEYWORD_PRINT, |s: &mut String| s.clone(), map);
reg_unary_mut(lib, FUNC_TO_STRING, |s: &mut String| s.clone(), map);
lib.set_fn_1_mut(KEYWORD_PRINT, |s: &mut String| Ok(s.clone()));
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);
@ -73,34 +73,34 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin
#[cfg(not(feature = "no_object"))]
{
reg_unary_mut(lib, KEYWORD_PRINT, format_map, map);
reg_unary_mut(lib, FUNC_TO_STRING, format_map, map);
reg_unary_mut(lib, KEYWORD_DEBUG, format_map, map);
lib.set_fn_1_mut(KEYWORD_PRINT, format_map);
lib.set_fn_1_mut(FUNC_TO_STRING, format_map);
lib.set_fn_1_mut(KEYWORD_DEBUG, format_map);
}
reg_binary(
lib,
lib.set_fn_2(
"+",
|mut s: String, ch: char| {
s.push(ch);
s
Ok(s)
},
map,
);
reg_binary(
lib,
lib.set_fn_2(
"+",
|mut s: String, s2: String| {
s.push_str(&s2);
s
Ok(s)
},
map,
);
reg_binary_mut(lib, "append", |s: &mut String, ch: char| s.push(ch), map);
reg_binary_mut(
lib,
lib.set_fn_2_mut("append", |s: &mut String, ch: char| {
s.push(ch);
Ok(())
});
lib.set_fn_2_mut(
"append",
|s: &mut String, s2: String| s.push_str(&s2),
map,
|s: &mut String, s2: String| {
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::fn_register::map_dynamic as map;
use crate::module::FuncReturn;
use crate::parser::INT;
#[cfg(not(feature = "no_index"))]
@ -14,19 +12,19 @@ use crate::stdlib::{
vec::Vec,
};
fn prepend<T: Display>(x: T, y: String) -> String {
format!("{}{}", x, y)
fn prepend<T: Display>(x: T, y: String) -> FuncReturn<String> {
Ok(format!("{}{}", x, y))
}
fn append<T: Display>(x: String, y: T) -> String {
format!("{}{}", x, y)
fn append<T: Display>(x: String, y: T) -> FuncReturn<String> {
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 {
return "".to_string();
return Ok("".to_string());
} else if start < 0 {
0
} else if (start as usize) >= s.chars().count() {
return "".to_string();
return Ok("".to_string());
} else {
start as usize
};
@ -39,17 +37,17 @@ fn sub_string(s: &mut String, start: INT, len: INT) -> String {
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 {
s.clear();
return;
return Ok(());
} else if start < 0 {
0
} else if (start as usize) >= s.chars().count() {
s.clear();
return;
return Ok(());
} else {
start as usize
};
@ -67,18 +65,22 @@ fn crop_string(s: &mut String, start: INT, len: INT) {
chars[offset..][..len]
.into_iter()
.for_each(|&ch| s.push(ch));
Ok(())
}
macro_rules! reg_op { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
$(reg_binary($lib, $op, $func::<$par>, map);)* };
macro_rules! reg_op {
($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, {
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_binary(lib, "+", |_: (), y: String| y, map);
lib.set_fn_2("+", |_: (), y: String| Ok(y));
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
@ -95,105 +97,95 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
#[cfg(not(feature = "no_index"))]
{
reg_binary(lib, "+", |x: String, y: Array| format!("{}{:?}", x, y), map);
reg_binary(lib, "+", |x: Array, y: String| format!("{:?}{}", x, y), map);
lib.set_fn_2("+", |x: String, y: Array| Ok(format!("{}{:?}", x, y)));
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);
reg_binary_mut(
lib,
lib.set_fn_1_mut("len", |s: &mut String| Ok(s.chars().count() as INT));
lib.set_fn_2_mut(
"contains",
|s: &mut String, ch: char| s.contains(ch),
map,
|s: &mut String, ch: char| Ok(s.contains(ch)),
);
reg_binary_mut(
lib,
lib.set_fn_2_mut(
"contains",
|s: &mut String, find: String| s.contains(&find),
map,
|s: &mut String, find: String| Ok(s.contains(&find)),
);
reg_trinary_mut(
lib,
lib.set_fn_3_mut(
"index_of",
|s: &mut String, ch: char, start: INT| {
let start = if start < 0 {
0
} else if (start as usize) >= s.chars().count() {
return -1 as INT;
return Ok(-1 as INT);
} else {
s.chars().take(start as usize).collect::<String>().len()
};
s[start..]
Ok(s[start..]
.find(ch)
.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,
lib.set_fn_2_mut(
"index_of",
|s: &mut String, ch: char| {
s.find(ch)
Ok(s.find(ch)
.map(|index| s[0..index].chars().count() as INT)
.unwrap_or(-1 as INT)
.unwrap_or(-1 as INT))
},
map,
);
reg_trinary_mut(
lib,
lib.set_fn_3_mut(
"index_of",
|s: &mut String, find: String, start: INT| {
let start = if start < 0 {
0
} else if (start as usize) >= s.chars().count() {
return -1 as INT;
return Ok(-1 as INT);
} else {
s.chars().take(start as usize).collect::<String>().len()
};
s[start..]
Ok(s[start..]
.find(&find)
.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,
lib.set_fn_2_mut(
"index_of",
|s: &mut String, find: String| {
s.find(&find)
Ok(s.find(&find)
.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);
reg_binary_mut(lib, "append", |s: &mut String, ch: char| s.push(ch), map);
reg_binary_mut(
lib,
lib.set_fn_1_mut("clear", |s: &mut String| {
s.clear();
Ok(())
});
lib.set_fn_2_mut( "append", |s: &mut String, ch: char| {
s.push(ch);
Ok(())
});
lib.set_fn_2_mut(
"append",
|s: &mut String, add: String| s.push_str(&add),
map,
|s: &mut String, add: String| {
s.push_str(&add);
Ok(())
}
);
reg_trinary_mut(lib, "sub_string", sub_string, map);
reg_binary_mut(
lib,
lib.set_fn_3_mut( "sub_string", sub_string);
lib.set_fn_2_mut(
"sub_string",
|s: &mut String, start: INT| sub_string(s, start, s.len() as INT),
map,
);
reg_trinary_mut(lib, "crop", crop_string, map);
reg_binary_mut(
lib,
lib.set_fn_3_mut( "crop", crop_string);
lib.set_fn_2_mut(
"crop",
|s: &mut String, start: INT| crop_string(s, start, s.len() as INT),
map,
);
reg_binary_mut(
lib,
lib.set_fn_2_mut(
"truncate",
|s: &mut String, len: INT| {
if len >= 0 {
@ -203,31 +195,55 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
} else {
s.clear();
}
Ok(())
},
map,
);
reg_trinary_mut(
lib,
lib.set_fn_3_mut(
"pad",
|s: &mut String, len: INT, ch: char| {
for _ in 0..s.chars().count() - len as usize {
s.push(ch);
}
Ok(())
},
map,
);
reg_trinary_mut(
lib,
lib.set_fn_3_mut(
"replace",
|s: &mut String, find: String, sub: String| {
let new_str = s.replace(&find, &sub);
s.clear();
s.push_str(&new_str);
Ok(())
},
map,
);
reg_unary_mut(
lib,
lib.set_fn_3_mut(
"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",
|s: &mut String| {
let trimmed = s.trim();
@ -235,7 +251,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
if trimmed.len() < s.len() {
*s = trimmed.to_string();
}
Ok(())
},
map,
);
});

View File

@ -1,9 +1,8 @@
use super::logic::{eq, gt, gte, lt, lte, ne};
use super::math_basic::MAX_INT;
use super::{reg_binary, reg_none, reg_unary};
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::result::EvalAltResult;
use crate::token::Position;
@ -14,10 +13,9 @@ use crate::stdlib::time::Instant;
#[cfg(not(feature = "no_std"))]
def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
// Register date/time functions
reg_none(lib, "timestamp", || Instant::now(), map);
lib.set_fn_0("timestamp", || Ok(Instant::now()));
reg_binary(
lib,
lib.set_fn_2(
"-",
|ts1: Instant, ts2: Instant| {
if ts2 > ts1 {
@ -63,18 +61,16 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
}
}
},
result,
);
reg_binary(lib, "<", lt::<Instant>, map);
reg_binary(lib, "<=", lte::<Instant>, map);
reg_binary(lib, ">", gt::<Instant>, map);
reg_binary(lib, ">=", gte::<Instant>, map);
reg_binary(lib, "==", eq::<Instant>, map);
reg_binary(lib, "!=", ne::<Instant>, map);
lib.set_fn_2("<", lt::<Instant>);
lib.set_fn_2("<=", lte::<Instant>);
lib.set_fn_2(">", gt::<Instant>);
lib.set_fn_2(">=", gte::<Instant>);
lib.set_fn_2("==", eq::<Instant>);
lib.set_fn_2("!=", ne::<Instant>);
reg_unary(
lib,
lib.set_fn_1(
"elapsed",
|timestamp: Instant| {
#[cfg(not(feature = "no_float"))]
@ -96,6 +92,5 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
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.
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.
/// Wrapped values are the name of the function, the number of parameters required
/// and the actual number of arguments passed.
@ -97,6 +100,7 @@ impl EvalAltResult {
Self::ErrorReadingScriptFile(_, _, _) => "Cannot read from script file",
Self::ErrorParsing(p) => p.desc(),
Self::ErrorInFunctionCall(_, _, _) => "Error in called function",
Self::ErrorFunctionNotFound(_, _) => "Function not found",
Self::ErrorFunctionArgsMismatch(_, _, _, _) => {
"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::ErrorInFunctionCall(s, err, pos) => {
write!(f, "Error in call to function '{}' ({}): {}", s, pos, err)
}
Self::ErrorFunctionNotFound(s, pos)
| Self::ErrorVariableNotFound(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 {
/// Get the `Position` of this error.
pub fn position(&self) -> Position {
match self {
#[cfg(not(feature = "no_std"))]
@ -270,6 +279,7 @@ impl EvalAltResult {
Self::ErrorParsing(err) => err.position(),
Self::ErrorFunctionNotFound(_, pos)
| Self::ErrorInFunctionCall(_, _, pos)
| Self::ErrorFunctionArgsMismatch(_, _, _, pos)
| Self::ErrorBooleanArgMismatch(_, pos)
| Self::ErrorCharMismatch(pos)
@ -296,16 +306,16 @@ impl EvalAltResult {
}
}
/// Consume the current `EvalAltResult` and return a new one
/// with the specified `Position`.
pub(crate) fn set_position(mut err: Box<Self>, new_position: Position) -> Box<Self> {
match err.as_mut() {
/// Override the `Position` of this error.
pub fn set_position(&mut self, new_position: Position) {
match self {
#[cfg(not(feature = "no_std"))]
Self::ErrorReadingScriptFile(_, pos, _) => *pos = new_position,
Self::ErrorParsing(err) => err.1 = new_position,
Self::ErrorFunctionNotFound(_, pos)
| Self::ErrorInFunctionCall(_, _, pos)
| Self::ErrorFunctionArgsMismatch(_, _, _, pos)
| Self::ErrorBooleanArgMismatch(_, pos)
| Self::ErrorCharMismatch(pos)
@ -330,7 +340,12 @@ impl EvalAltResult {
| Self::ErrorLoopBreak(_, pos)
| 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"))]
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.
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
@ -22,7 +22,7 @@ pub enum EntryType {
}
/// An entry in the Scope.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Entry<'a> {
/// Name of the entry.
pub name: Cow<'a, str>,
@ -30,6 +30,8 @@ pub struct Entry<'a> {
pub typ: EntryType,
/// Current value of the entry.
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.
pub expr: Option<Box<Expr>>,
}
@ -62,7 +64,7 @@ pub struct Entry<'a> {
/// allowing for automatic _shadowing_.
///
/// Currently, `Scope` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`.
#[derive(Debug, Default)]
#[derive(Debug, Clone, Default)]
pub struct Scope<'a>(Vec<Entry<'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.
#[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(
name,
EntryType::Module,
@ -246,6 +250,7 @@ impl<'a> Scope<'a> {
self.0.push(Entry {
name: name.into(),
typ: entry_type,
alias: None,
value: value.into(),
expr,
});
@ -410,16 +415,15 @@ impl<'a> Scope<'a> {
/// Get a mutable reference to an entry in the Scope.
pub(crate) fn get_mut(&mut self, index: usize) -> (&mut Dynamic, EntryType) {
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)
}
/// 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.
pub(crate) fn into_iter(self) -> impl Iterator<Item = Entry<'a>> {
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 {
name: name.into(),
typ,
alias: None,
value: value.into(),
expr: None,
}));

View File

@ -196,6 +196,7 @@ pub enum Token {
XOrAssign,
ModuloAssign,
PowerOfAssign,
Private,
Import,
Export,
As,
@ -205,14 +206,14 @@ pub enum Token {
impl Token {
/// Get the syntax of the token.
pub fn syntax(&self) -> Cow<str> {
pub fn syntax(&self) -> Cow<'static, str> {
use Token::*;
match self {
IntegerConstant(i) => i.to_string().into(),
#[cfg(not(feature = "no_float"))]
FloatConstant(f) => f.to_string().into(),
Identifier(s) => s.into(),
Identifier(s) => s.clone().into(),
CharConstant(c) => c.to_string().into(),
LexError(err) => err.to_string().into(),
@ -279,6 +280,7 @@ impl Token {
ModuloAssign => "%=",
PowerOf => "~",
PowerOfAssign => "~=",
Private => "private",
Import => "import",
Export => "export",
As => "as",
@ -750,6 +752,7 @@ impl<'a> TokenIterator<'a> {
"throw" => Token::Throw,
"for" => Token::For,
"in" => Token::In,
"private" => Token::Private,
#[cfg(not(feature = "no_module"))]
"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.
use crate::r#unsafe::unsafe_zeroed;
use crate::stdlib::{
any::TypeId,
fmt,
hash::{Hash, Hasher},
iter::FromIterator,
mem,
vec::Vec,
};
@ -14,30 +17,33 @@ use crate::stdlib::collections::hash_map::DefaultHasher;
#[cfg(feature = "no_std")]
use ahash::AHasher;
/// Calculate a `u64` hash key from a function name and parameter types.
///
/// Parameter types are passed in via `TypeId` values from an iterator
/// which can come from any source.
pub fn calc_fn_spec(fn_name: &str, params: impl Iterator<Item = TypeId>) -> u64 {
#[cfg(feature = "no_std")]
let mut s: AHasher = Default::default();
#[cfg(not(feature = "no_std"))]
let mut s = DefaultHasher::new();
s.write(fn_name.as_bytes());
params.for_each(|t| t.hash(&mut s));
s.finish()
#[inline(always)]
pub fn EMPTY_TYPE_ID() -> TypeId {
TypeId::of::<()>()
}
/// Calculate a `u64` hash key from a function name and number of parameters (without regard to types).
pub(crate) fn calc_fn_def(fn_name: &str, num_params: usize) -> u64 {
/// Calculate a `u64` hash key from a module-qualified function name and parameter types.
///
/// 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")]
let mut s: AHasher = Default::default();
#[cfg(not(feature = "no_std"))]
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_usize(num_params);
params.for_each(|t| t.hash(&mut s));
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 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.
len: usize,
/// 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>,
}
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`.
pub fn new() -> Self {
Default::default()
}
/// Push a new value to the end of this `StaticVec`.
pub fn push<X: Into<T>>(&mut self, value: X) {
if self.len >= self.list.len() {
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());
} else {
self.list[self.len] = value.into();
@ -78,9 +128,19 @@ impl<T: Default + Clone> StaticVec<T> {
let result = if self.len <= 0 {
panic!("nothing to pop!")
} else if self.len <= self.list.len() {
mem::take(self.list.get_mut(self.len - 1).unwrap())
let def_val: T = unsafe_zeroed();
mem::replace(self.list.get_mut(self.len - 1).unwrap(), def_val)
} 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;
@ -91,38 +151,99 @@ impl<T: Default + Clone> StaticVec<T> {
pub fn len(&self) -> usize {
self.len
}
/// Get an item at a particular index.
/// Get a reference to the item at a particular index.
///
/// # Panics
///
/// 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 {
panic!("index OOB in StaticVec");
}
if index < self.list.len() {
if self.len < self.list.len() {
self.list.get(index).unwrap()
} 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`.
pub fn iter(&self) -> impl Iterator<Item = &T> {
let num = if self.len >= self.list.len() {
self.list.len()
if self.len > self.list.len() {
self.more.iter()
} else {
self.len
};
self.list[..num].iter().chain(self.more.iter())
self.list[..self.len].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 {
write!(f, "[ ")?;
self.iter().try_for_each(|v| write!(f, "{:?}, ", v))?;
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);
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);
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!(
@ -60,6 +60,27 @@ fn test_call_fn() -> Result<(), Box<EvalAltResult>> {
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]
fn test_anonymous_fn() -> Result<(), Box<EvalAltResult>> {
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);
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(())
}

View File

@ -1,4 +1,4 @@
use rhai::{Engine, EvalAltResult, INT};
use rhai::{Engine, EvalAltResult, ParseErrorType, INT};
#[test]
fn test_constant() -> Result<(), Box<EvalAltResult>> {
@ -8,13 +8,13 @@ fn test_constant() -> Result<(), Box<EvalAltResult>> {
assert!(matches!(
*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"))]
assert!(matches!(
*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(())

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 {
fn update(&mut self) {
self.x += 1000;
fn update(&mut self, n: INT) {
self.x += n;
}
fn new() -> Self {
@ -27,14 +27,23 @@ fn test_method_call() -> Result<(), Box<EvalAltResult>> {
engine.register_fn("new_ts", TestStruct::new);
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 }
);
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 }
);
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();
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);
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();
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);
let mut engine = Engine::new();
let engine = Engine::new();
let mut scope = Scope::new();
scope.push_module("question", module);
@ -81,3 +81,87 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
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"),
Err(err) => match *err {
EvalAltResult::ErrorStackOverflow(_) => (),
EvalAltResult::ErrorInFunctionCall(name, _, _)
if name.starts_with("foo > foo > foo") => {}
_ => panic!("should be stack overflow"),
},
}