Merge branch 'master' into plugins
This commit is contained in:
commit
95490adf0b
103
README.md
103
README.md
@ -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`:
|
||||
|
||||
|
56
src/any.rs
56
src/any.rs
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
18
src/api.rs
18
src/api.rs
@ -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());
|
||||
|
||||
|
819
src/engine.rs
819
src/engine.rs
File diff suppressed because it is too large
Load Diff
30
src/error.rs
30
src/error.rs
@ -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() => {
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
372
src/module.rs
372
src/module.rs
@ -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>> {
|
||||
|
347
src/optimize.rs
347
src/optimize.rs
@ -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
|
||||
})
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
});
|
||||
|
||||
|
1277
src/parser.rs
1277
src/parser.rs
File diff suppressed because it is too large
Load Diff
23
src/scope.rs
23
src/scope.rs
@ -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,
|
||||
}));
|
||||
|
@ -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,
|
||||
|
46
src/utils.rs
46
src/utils.rs
@ -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> {
|
||||
|
@ -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(())
|
||||
|
@ -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(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user