Merge branch 'master' into plugins

This commit is contained in:
Stephen Chung 2020-05-10 00:14:03 +08:00
commit 95490adf0b
16 changed files with 2090 additions and 1244 deletions

103
README.md
View File

@ -268,6 +268,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 +288,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 +306,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
@ -2044,10 +2054,34 @@ Using external modules
[module]: #using-external-modules
[modules]: #using-external-modules
Rhai allows organizing code (functions and variables) into _modules_. A module is a single script file
with `export` statements that _exports_ certain global variables and functions as contents of the module.
Rhai allows organizing code (functions 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
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.
All functions are automatically exported, unless it is prefixed with `private`.
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 } // public function
private fn foo() {} // private function - invisible to outside
let private = 123; // variable not exported - 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
@ -2080,7 +2114,7 @@ crypto::encrypt(others); // <- this causes a run-time error because the 'cryp
### Creating custom modules from 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 +2139,55 @@ 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 nothing inside the module!
```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 +2197,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

@ -18,7 +18,7 @@ use crate::stdlib::{
any::{type_name, Any, TypeId},
boxed::Box,
collections::HashMap,
fmt,
fmt, mem, ptr,
string::String,
vec::Vec,
};
@ -38,6 +38,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 +63,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 +92,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<dyn Any>;
/// Get the name of this type.
fn type_name(&self) -> &'static str;
@ -108,6 +117,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>()
}
@ -284,6 +296,22 @@ impl Default for Dynamic {
}
}
/// Cast a type into another type.
fn 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.
fn cast_box<X: Variant, T: Variant>(item: Box<X>) -> Result<Box<T>, Box<X>> {
// Only allow casting to the exact same type
@ -388,26 +416,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);
}
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::Unit(value) => try_cast(value),
Union::Bool(value) => try_cast(value),
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::Char(value) => try_cast(value),
Union::Int(value) => try_cast(value),
#[cfg(not(feature = "no_float"))]
Union::Float(ref value) => (value as &dyn Any).downcast_ref::<T>().cloned(),
Union::Float(value) => try_cast(value),
#[cfg(not(feature = "no_index"))]
Union::Array(value) => cast_box::<_, T>(value).ok().map(|v| *v),
#[cfg(not(feature = "no_object"))]
Union::Map(value) => cast_box::<_, T>(value).ok().map(|v| *v),
#[cfg(not(feature = "no_module"))]
Union::Module(value) => cast_box::<_, T>(value).ok().map(|v| *v),
Union::Variant(value) => value.as_any().downcast_ref::<T>().cloned(),
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(),
}
}
@ -435,20 +463,20 @@ impl Dynamic {
}
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::Unit(value) => try_cast(value).unwrap(),
Union::Bool(value) => try_cast(value).unwrap(),
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::Char(value) => try_cast(value).unwrap(),
Union::Int(value) => 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) => try_cast(value).unwrap(),
#[cfg(not(feature = "no_index"))]
Union::Array(value) => *cast_box::<_, T>(value).unwrap(),
#[cfg(not(feature = "no_object"))]
Union::Map(value) => *cast_box::<_, T>(value).unwrap(),
#[cfg(not(feature = "no_module"))]
Union::Module(value) => *cast_box::<_, T>(value).unwrap(),
Union::Variant(value) => value.as_any().downcast_ref::<T>().unwrap().clone(),
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).unwrap(),
}
}

View File

@ -168,7 +168,9 @@ 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.base_package
.type_iterators
.insert(TypeId::of::<T>(), Box::new(f));
}
/// Register a getter function for a member of a registered type with the `Engine`.
@ -867,12 +869,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 +934,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 {
@ -997,10 +999,12 @@ impl Engine {
let pos = Position::none();
let fn_def = fn_lib
.get_function(name, args.len())
.get_function_by_signature(name, args.len(), true)
.ok_or_else(|| Box::new(EvalAltResult::ErrorFunctionNotFound(name.to_string(), pos)))?;
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, fn_def, &mut args, 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, PartialEq, Eq, 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

@ -8,7 +8,7 @@ use crate::result::EvalAltResult;
use crate::token::Position;
use crate::utils::calc_fn_spec;
use crate::stdlib::{any::TypeId, boxed::Box, mem, string::ToString};
use crate::stdlib::{any::TypeId, boxed::Box, iter::empty, mem, string::ToString};
/// A trait to register custom plugins with the `Engine`.
///
@ -279,8 +279,8 @@ macro_rules! def_register {
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));
let hash = calc_fn_spec(empty(), name, [$(TypeId::of::<$par>()),*].iter().cloned());
self.base_package.functions.insert(hash, Box::new(func));
}
}
@ -297,8 +297,8 @@ macro_rules! def_register {
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));
let hash = calc_fn_spec(empty(), name, [$(TypeId::of::<$par>()),*].iter().cloned());
self.base_package.functions.insert(hash, Box::new(func));
}
}
@ -316,8 +316,8 @@ macro_rules! def_register {
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));
let hash = calc_fn_spec(empty(), name, [$(TypeId::of::<$par>()),*].iter().cloned());
self.base_package.functions.insert(hash, Box::new(func));
}
}

View File

@ -3,23 +3,27 @@
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, FnAny, FnCallArgs, FunctionsLib, NativeFunction, ScriptedFunction};
use crate::parser::{FnAccess, FnDef, AST};
use crate::result::EvalAltResult;
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
use crate::token::Position;
use crate::token::Token;
use crate::utils::StaticVec;
use crate::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.
@ -28,6 +32,7 @@ pub trait ModuleResolver {
fn resolve(
&self,
engine: &Engine,
scope: Scope,
path: &str,
pos: Position,
) -> Result<Module, Box<EvalAltResult>>;
@ -44,18 +49,24 @@ type FuncReturn<T> = Result<T, Box<EvalAltResult>>;
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>, NativeFunction)>,
/// Flattened collection of all external Rust functions, including those in sub-modules.
all_functions: HashMap<u64, NativeFunction>,
/// Script-defined functions.
fn_lib: FunctionsLib,
/// Flattened collection of all script-defined functions, including those in sub-modules.
all_fn_lib: FunctionsLib,
}
impl fmt::Debug for Module {
@ -113,7 +124,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 +142,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.
@ -154,16 +160,17 @@ impl Module {
}
/// 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: 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)
.ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.to_string(), pos)))
}
/// Does a sub-module exist in the module?
@ -232,26 +239,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`.
@ -273,13 +260,21 @@ impl Module {
/// 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,
fn_name: String,
access: FnAccess,
params: Vec<TypeId>,
func: Box<FnAny>,
) -> u64 {
let hash = calc_fn_hash(empty(), &fn_name, params.iter().cloned());
#[cfg(not(feature = "sync"))]
self.functions.insert(hash, Rc::new(func));
self.functions
.insert(hash, (fn_name, access, params, Rc::new(func)));
#[cfg(feature = "sync")]
self.functions.insert(hash, Arc::new(func));
self.functions
.insert(hash, (fn_name, access, params, Arc::new(func)));
hash
}
@ -297,9 +292,9 @@ 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: Into<Dynamic>>(
&mut self,
fn_name: &str,
fn_name: K,
#[cfg(not(feature = "sync"))] func: impl Fn() -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn() -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 {
@ -308,8 +303,8 @@ impl Module {
.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 arg_types = vec![];
self.set_fn(fn_name.into(), FnAccess::Public, arg_types, Box::new(f))
}
/// Set a Rust function taking one parameter into the module, returning a hash key.
@ -325,9 +320,9 @@ 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: Into<Dynamic>>(
&mut self,
fn_name: &str,
fn_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 {
@ -336,8 +331,8 @@ impl Module {
.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 arg_types = vec![TypeId::of::<A>()];
self.set_fn(fn_name.into(), FnAccess::Public, arg_types, Box::new(f))
}
/// Set a Rust function taking one mutable parameter into the module, returning a hash key.
@ -353,9 +348,9 @@ 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: Into<Dynamic>>(
&mut self,
fn_name: &str,
fn_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 {
@ -364,8 +359,8 @@ impl Module {
.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 arg_types = vec![TypeId::of::<A>()];
self.set_fn(fn_name.into(), FnAccess::Public, arg_types, Box::new(f))
}
/// Set a Rust function taking two parameters into the module, returning a hash key.
@ -383,9 +378,9 @@ 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: Into<Dynamic>>(
&mut self,
fn_name: &str,
fn_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 {
@ -397,8 +392,8 @@ impl Module {
.map(|v| v.into())
.map_err(|err| EvalAltResult::set_position(err, pos))
};
let arg_types = &[TypeId::of::<A>(), TypeId::of::<B>()];
self.set_fn(fn_name, arg_types, Box::new(f))
let arg_types = vec![TypeId::of::<A>(), TypeId::of::<B>()];
self.set_fn(fn_name.into(), FnAccess::Public, arg_types, Box::new(f))
}
/// Set a Rust function taking two parameters (the first one mutable) into the module,
@ -415,9 +410,14 @@ 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: Into<Dynamic>,
>(
&mut self,
fn_name: &str,
fn_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 {
@ -429,8 +429,8 @@ impl Module {
.map(|v| v.into())
.map_err(|err| EvalAltResult::set_position(err, pos))
};
let arg_types = &[TypeId::of::<A>(), TypeId::of::<B>()];
self.set_fn(fn_name, arg_types, Box::new(f))
let arg_types = vec![TypeId::of::<A>(), TypeId::of::<B>()];
self.set_fn(fn_name.into(), FnAccess::Public, arg_types, Box::new(f))
}
/// Set a Rust function taking three parameters into the module, returning a hash key.
@ -449,13 +449,14 @@ 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>,
>(
&mut self,
fn_name: &str,
fn_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 {
@ -468,8 +469,8 @@ impl Module {
.map(|v| v.into())
.map_err(|err| EvalAltResult::set_position(err, pos))
};
let arg_types = &[TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn(fn_name, arg_types, Box::new(f))
let arg_types = vec![TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn(fn_name.into(), FnAccess::Public, arg_types, Box::new(f))
}
/// Set a Rust function taking three parameters (the first one mutable) into the module,
@ -489,13 +490,14 @@ 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>,
>(
&mut self,
fn_name: &str,
fn_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 {
@ -508,8 +510,8 @@ impl Module {
.map(|v| v.into())
.map_err(|err| EvalAltResult::set_position(err, pos))
};
let arg_types = &[TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn(fn_name, arg_types, Box::new(f))
let arg_types = vec![TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn(fn_name.into(), FnAccess::Public, arg_types, Box::new(f))
}
/// Get a Rust function.
@ -527,7 +529,7 @@ impl Module {
/// 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())
self.functions.get(&hash).map(|(_, _, _, v)| v.as_ref())
}
/// Get a modules-qualified function.
@ -538,24 +540,12 @@ impl Module {
&mut self,
name: &str,
hash: u64,
modules: &StaticVec<(String, Position)>,
pos: Position,
) -> Result<&Box<FnAny>, Box<EvalAltResult>> {
Ok(self
.get_qualified_module_mut(modules)?
.get_fn(hash)
.ok_or_else(|| {
let mut fn_name: String = Default::default();
modules.iter().for_each(|(n, _)| {
fn_name.push_str(n);
fn_name.push_str(Token::DoubleColon.syntax().as_ref());
});
fn_name.push_str(name);
Box::new(EvalAltResult::ErrorFunctionNotFound(fn_name, pos))
})?)
self.all_functions
.get(&hash)
.map(|f| f.as_ref())
.ok_or_else(|| Box::new(EvalAltResult::ErrorFunctionNotFound(name.to_string(), pos)))
}
/// Get the script-defined functions.
@ -572,17 +562,11 @@ impl Module {
&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))
/// Get a modules-qualified script-defined functions.
///
/// The `u64` hash is calculated by the function `crate::calc_fn_hash`.
pub(crate) fn get_qualified_scripted_fn(&mut self, hash: u64) -> Option<&FnDef> {
self.all_fn_lib.get_function(hash)
}
/// Create a new `Module` by evaluating an `AST`.
@ -591,20 +575,17 @@ 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();
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 +594,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,6 +617,87 @@ 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, NativeFunction)>,
fn_lib: &mut Vec<(u64, ScriptedFunction)>,
) {
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 = calc_fn_hash(qualifiers.iter().map(|v| *v), var_name, empty());
variables.push((hash, value.clone()));
}
// Index all Rust functions
for (fn_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),
fn_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 = hash_fn_def ^ hash_fn_args;
functions.push((hash, 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,
FnAccess::Public => (),
}
// Qualifiers + function name + placeholders (one for each parameter)
let hash = 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.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();
}
}
/// Re-export module resolvers.
@ -669,12 +733,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, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
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 +810,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>> {
@ -759,12 +840,70 @@ mod file {
.compile_file(file_path)
.map_err(|err| EvalAltResult::set_position(err, pos))?;
Module::eval_ast_as_new(&ast, engine)
Module::eval_ast_as_new(scope, &ast, engine)
.map_err(|err| EvalAltResult::set_position(err, pos))
}
}
}
/// 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, 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
}
}
/// Static module resolver.
mod stat {
use super::*;
@ -828,6 +967,7 @@ mod stat {
fn resolve(
&self,
_: &Engine,
_: Scope,
path: &str,
pos: Position,
) -> Result<Module, Box<EvalAltResult>> {

View File

@ -4,7 +4,7 @@ use crate::engine::{
Engine, FnAny, FnCallArgs, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT,
KEYWORD_TYPE_OF,
};
use crate::packages::PackageLibrary;
use crate::packages::{PackageStore, 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 +13,7 @@ use crate::token::Position;
use crate::stdlib::{
boxed::Box,
collections::HashMap,
iter::empty,
string::{String, ToString},
vec,
vec::Vec,
@ -110,23 +111,18 @@ impl<'a> State<'a> {
/// Call a registered function
fn call_fn(
packages: &Vec<PackageLibrary>,
functions: &HashMap<u64, Box<FnAny>>,
packages: &PackagesCollection,
base_package: &PackageStore,
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))
})
base_package
.get_function(hash)
.or_else(|| packages.get_function(hash))
.map(|func| func(args, pos))
.transpose()
}
@ -135,63 +131,63 @@ fn call_fn(
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 +196,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 +214,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 +265,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 +299,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 +323,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 +350,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 +362,128 @@ 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()) => {
// 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 == &p.0)
.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 +500,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 +520,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 +549,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.base_package, name, &mut call_args, *pos).ok()
.and_then(|result|
result.or_else(|| {
if !arg_for_type_of.is_empty() {
@ -585,25 +563,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 +642,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)
@ -731,19 +714,25 @@ pub fn optimize_into_ast(
// Optimize the function body
let mut body =
optimize(vec![*fn_def.body], engine, &Scope::new(), &fn_lib, level);
optimize(vec![fn_def.body], engine, &Scope::new(), &fn_lib, level);
// {} -> Noop
fn_def.body = Box::new(match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
// { return val; } -> val
Stmt::ReturnWithVal(Some(val), ReturnType::Return, _) => Stmt::Expr(val),
Stmt::ReturnWithVal(x)
if x.1.is_some() && (x.0).0 == ReturnType::Return =>
{
Stmt::Expr(Box::new(x.1.unwrap()))
}
// { return; } -> ()
Stmt::ReturnWithVal(None, ReturnType::Return, pos) => {
Stmt::Expr(Box::new(Expr::Unit(pos)))
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
})

View File

@ -2,7 +2,7 @@
use crate::engine::{FnAny, IteratorFn};
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;
@ -34,11 +34,10 @@ pub use time_basic::BasicTimePackage;
pub use utils::*;
const NUM_NATIVE_FUNCTIONS: usize = 512;
/// 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);
@ -47,7 +46,6 @@ pub trait Package {
}
/// Type to store all functions in the package.
#[derive(Default)]
pub struct PackageStore {
/// All functions, keyed by a hash created from the function name and parameter types.
pub functions: HashMap<u64, Box<FnAny>>,
@ -61,6 +59,31 @@ impl PackageStore {
pub fn new() -> Self {
Default::default()
}
/// Does the specified function hash key exist in the `PackageStore`?
pub fn contains_function(&self, hash: u64) -> bool {
self.functions.contains_key(&hash)
}
/// Get specified function via its hash key.
pub fn get_function(&self, hash: u64) -> Option<&Box<FnAny>> {
self.functions.get(&hash)
}
/// Does the specified TypeId iterator exist in the `PackageStore`?
pub fn contains_iterator(&self, id: TypeId) -> bool {
self.type_iterators.contains_key(&id)
}
/// Get the specified TypeId iterator.
pub fn get_iterator(&self, id: TypeId) -> Option<&Box<IteratorFn>> {
self.type_iterators.get(&id)
}
}
impl Default for PackageStore {
fn default() -> Self {
Self {
functions: HashMap::with_capacity(NUM_NATIVE_FUNCTIONS),
type_iterators: HashMap::with_capacity(4),
}
}
}
/// Type which `Rc`-wraps a `PackageStore` to facilitate sharing library instances.
@ -70,3 +93,43 @@ pub type PackageLibrary = Rc<PackageStore>;
/// Type which `Arc`-wraps a `PackageStore` to facilitate sharing library instances.
#[cfg(feature = "sync")]
pub type PackageLibrary = Arc<PackageStore>;
/// 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 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_function(&self, hash: u64) -> bool {
self.packages.iter().any(|p| p.contains_function(hash))
}
/// Get specified function via its hash key.
pub fn get_function(&self, hash: u64) -> Option<&Box<FnAny>> {
self.packages
.iter()
.map(|p| p.get_function(hash))
.find(|f| f.is_some())
.flatten()
}
/// Does the specified TypeId iterator exist in the `PackagesCollection`?
pub fn contains_iterator(&self, id: TypeId) -> bool {
self.packages.iter().any(|p| p.contains_iterator(id))
}
/// Get the specified TypeId iterator.
pub fn get_iterator(&self, id: TypeId) -> Option<&Box<IteratorFn>> {
self.packages
.iter()
.map(|p| p.get_iterator(id))
.find(|f| f.is_some())
.flatten()
}
}

View File

@ -9,6 +9,8 @@ use crate::token::Position;
use crate::stdlib::{
any::TypeId,
boxed::Box,
iter::empty,
mem,
string::{String, ToString},
};
@ -42,12 +44,6 @@ macro_rules! def_package {
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()
}
@ -56,6 +52,14 @@ macro_rules! def_package {
$block
}
}
impl $package {
pub fn new() -> Self {
let mut pkg = $root::packages::PackageStore::new();
<Self as $root::packages::Package>::init(&mut pkg);
Self(pkg.into())
}
}
};
}
@ -114,7 +118,7 @@ pub fn reg_none<R>(
+ Sync
+ 'static,
) {
let hash = calc_fn_hash(fn_name, ([] as [TypeId; 0]).iter().cloned());
let hash = calc_fn_hash(empty(), 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)?;
@ -164,15 +168,15 @@ pub fn reg_unary<T: Variant + Clone, R>(
) {
//println!("register {}({})", fn_name, crate::std::any::type_name::<T>());
let hash = calc_fn_hash(fn_name, [TypeId::of::<T>()].iter().cloned());
let hash = calc_fn_hash(empty(), 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 x = mem::take(*drain.next().unwrap()).cast::<T>();
let r = func(x.clone());
let r = func(x);
map_result(r, pos)
});
@ -224,7 +228,7 @@ pub fn reg_unary_mut<T: Variant + Clone, R>(
) {
//println!("register {}(&mut {})", fn_name, crate::std::any::type_name::<T>());
let hash = calc_fn_hash(fn_name, [TypeId::of::<T>()].iter().cloned());
let hash = calc_fn_hash(empty(), 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)?;
@ -278,6 +282,7 @@ pub fn reg_binary<A: Variant + Clone, B: Variant + Clone, R>(
//println!("register {}({}, {})", fn_name, crate::std::any::type_name::<A>(), crate::std::any::type_name::<B>());
let hash = calc_fn_hash(
empty(),
fn_name,
[TypeId::of::<A>(), TypeId::of::<B>()].iter().cloned(),
);
@ -286,10 +291,10 @@ pub fn reg_binary<A: Variant + Clone, B: Variant + Clone, R>(
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: &mut B = drain.next().unwrap().downcast_mut().unwrap();
let x = mem::take(*drain.next().unwrap()).cast::<A>();
let y = mem::take(*drain.next().unwrap()).cast::<B>();
let r = func(x.clone(), y.clone());
let r = func(x, y);
map_result(r, pos)
});
@ -342,6 +347,7 @@ pub fn reg_binary_mut<A: Variant + Clone, B: Variant + Clone, R>(
//println!("register {}(&mut {}, {})", fn_name, crate::std::any::type_name::<A>(), crate::std::any::type_name::<B>());
let hash = calc_fn_hash(
empty(),
fn_name,
[TypeId::of::<A>(), TypeId::of::<B>()].iter().cloned(),
);
@ -351,9 +357,9 @@ pub fn reg_binary_mut<A: Variant + Clone, B: Variant + Clone, R>(
let mut drain = args.iter_mut();
let x: &mut A = drain.next().unwrap().downcast_mut().unwrap();
let y: &mut B = drain.next().unwrap().downcast_mut().unwrap();
let y = mem::take(*drain.next().unwrap()).cast::<B>();
let r = func(x, y.clone());
let r = func(x, y);
map_result(r, pos)
});
@ -380,6 +386,7 @@ pub fn reg_trinary<A: Variant + Clone, B: Variant + Clone, C: Variant + Clone, R
//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(
empty(),
fn_name,
[TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()]
.iter()
@ -390,11 +397,11 @@ pub fn reg_trinary<A: Variant + Clone, B: Variant + Clone, C: Variant + Clone, R
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: &mut B = drain.next().unwrap().downcast_mut().unwrap();
let z: &mut C = drain.next().unwrap().downcast_mut().unwrap();
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.clone(), y.clone(), z.clone());
let r = func(x, y, z);
map_result(r, pos)
});
@ -421,6 +428,7 @@ pub fn reg_trinary_mut<A: Variant + Clone, B: Variant + Clone, C: Variant + Clon
//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(
empty(),
fn_name,
[TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()]
.iter()
@ -432,10 +440,10 @@ pub fn reg_trinary_mut<A: Variant + Clone, B: Variant + Clone, C: Variant + Clon
let mut drain = args.iter_mut();
let x: &mut A = drain.next().unwrap().downcast_mut().unwrap();
let y: &mut B = drain.next().unwrap().downcast_mut().unwrap();
let z: &mut C = 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.clone(), z.clone());
let r = func(x, y, z);
map_result(r, pos)
});

File diff suppressed because it is too large Load Diff

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, vec::Vec};
/// Type of an entry in the Scope.
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
@ -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>>,
}
@ -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,

View File

@ -14,30 +14,32 @@ 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()
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,7 +47,7 @@ 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)]
#[derive(Clone, Hash, Default)]
pub struct StaticVec<T: Default + Clone> {
/// Total number of values held.
len: usize,
@ -117,6 +119,16 @@ impl<T: Default + Clone> StaticVec<T> {
self.list[..num].iter().chain(self.more.iter())
}
/// Get a mutable iterator to entries in the `StaticVec`.
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
let num = if self.len >= self.list.len() {
self.list.len()
} else {
self.len
};
self.list[..num].iter_mut().chain(self.more.iter_mut())
}
}
impl<T: Default + Clone + fmt::Debug> fmt::Debug for StaticVec<T> {

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

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,86 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
Ok(())
}
#[test]
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 + y.len()
}
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(())
}