diff --git a/Cargo.toml b/Cargo.toml index 431473e1..d7c17ec9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rhai" -version = "0.13.0" +version = "0.14.1" edition = "2018" authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"] description = "Embedded scripting for Rust" @@ -20,7 +20,7 @@ categories = [ "no-std", "embedded", "parser-implementations" ] num-traits = { version = "0.2.11", default-features = false } [features] -#default = ["no_stdlib", "no_function", "no_index", "no_object", "no_float", "only_i32", "unchecked", "no_optimize", "sync"] +#default = ["no_stdlib", "no_function", "no_index", "no_object", "no_module", "no_float", "only_i32", "unchecked", "no_optimize", "sync"] default = [] plugins = [] unchecked = [] # unchecked arithmetic @@ -29,6 +29,7 @@ no_float = [] # no floating-point no_function = [] # no script-defined functions no_object = [] # no custom objects no_optimize = [] # no script optimizer +no_module = [] # no modules only_i32 = [] # set INT=i32 (useful for 32-bit systems) only_i64 = [] # set INT=i64 (default) and disable support for all other integer types sync = [] # restrict to only types that implement Send + Sync diff --git a/README.md b/README.md index 02b3e27b..cd95c92f 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Rhai's current features set: * Easy-to-use language similar to JS+Rust * Easy integration with Rust [native functions](#working-with-functions) and [types](#custom-types-and-methods), - including [getter/setter](#getters-and-setters)/[methods](#members-and-methods) + including [getters/setters](#getters-and-setters), [methods](#members-and-methods) and [indexers](#indexers) * Easily [call a script-defined function](#calling-rhai-functions-from-rust) from Rust * Freely pass variables/constants into a script via an external [`Scope`] * Fairly efficient (1 million iterations in 0.75 sec on my 5 year old laptop) @@ -23,13 +23,14 @@ Rhai's current features set: * [`no-std`](#optional-features) support * Support for [function overloading](#function-overloading) * Support for [operator overloading](#operator-overloading) +* Support for loading external [modules] * Compiled script is [optimized](#script-optimization) for repeat evaluations * Support for [minimal builds](#minimal-builds) by excluding unneeded language [features](#optional-features) * Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/) to do checked arithmetic operations); for [`no-std`](#optional-features) builds, a number of additional dependencies are pulled in to provide for functionalities that used to be in `std`. -**Note:** Currently, the version is 0.13.0, so the language and API's may change before they stabilize. +**Note:** Currently, the version is 0.14.1, so the language and API's may change before they stabilize. Installation ------------ @@ -38,7 +39,7 @@ Install the Rhai crate by adding this line to `dependencies`: ```toml [dependencies] -rhai = "0.13.0" +rhai = "0.14.1" ``` Use the latest released crate version on [`crates.io`](https::/crates.io/crates/rhai/): @@ -69,6 +70,7 @@ Optional features | `no_object` | Disable support for custom types and objects. | | `no_float` | Disable floating-point numbers and math if not needed. | | `no_optimize` | Disable the script optimizer. | +| `no_module` | Disable modules. | | `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | @@ -84,6 +86,7 @@ Excluding unneeded functionalities can result in smaller, faster builds as well [`no_function`]: #optional-features [`no_object`]: #optional-features [`no_optimize`]: #optional-features +[`no_module`]: #optional-features [`only_i32`]: #optional-features [`only_i64`]: #optional-features [`no_std`]: #optional-features @@ -374,19 +377,19 @@ engine.load_package(package.get()); // load the package manually The follow packages are available: -| Package | Description | In `CorePackage` | In `StandardPackage` | -| ------------------------ | ----------------------------------------------- | :--------------: | :------------------: | -| `ArithmeticPackage` | Arithmetic operators (e.g. `+`, `-`, `*`, `/`) | Yes | Yes | -| `BasicIteratorPackage` | Numeric ranges (e.g. `range(1, 10)`) | Yes | Yes | -| `LogicPackage` | Logic and comparison operators (e.g. `==`, `>`) | Yes | Yes | -| `BasicStringPackage` | Basic string functions | Yes | Yes | -| `BasicTimePackage` | Basic time functions (e.g. [timestamps]) | Yes | Yes | -| `MoreStringPackage` | Additional string functions | No | Yes | -| `BasicMathPackage` | Basic math functions (e.g. `sin`, `sqrt`) | No | Yes | -| `BasicArrayPackage` | Basic [array] functions | No | Yes | -| `BasicMapPackage` | Basic [object map] functions | No | Yes | -| `CorePackage` | Basic essentials | | | -| `StandardPackage` | Standard library | | | +| Package | Description | In `CorePackage` | In `StandardPackage` | +| ---------------------- | ----------------------------------------------- | :--------------: | :------------------: | +| `ArithmeticPackage` | Arithmetic operators (e.g. `+`, `-`, `*`, `/`) | Yes | Yes | +| `BasicIteratorPackage` | Numeric ranges (e.g. `range(1, 10)`) | Yes | Yes | +| `LogicPackage` | Logic and comparison operators (e.g. `==`, `>`) | Yes | Yes | +| `BasicStringPackage` | Basic string functions | Yes | Yes | +| `BasicTimePackage` | Basic time functions (e.g. [timestamps]) | Yes | Yes | +| `MoreStringPackage` | Additional string functions | No | Yes | +| `BasicMathPackage` | Basic math functions (e.g. `sin`, `sqrt`) | No | Yes | +| `BasicArrayPackage` | Basic [array] functions | No | Yes | +| `BasicMapPackage` | Basic [object map] functions | No | Yes | +| `CorePackage` | Basic essentials | | | +| `StandardPackage` | Standard library | | | Evaluate expressions only ------------------------- @@ -427,7 +430,7 @@ The following primitive types are supported natively: | **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` | | **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. | | **Unicode string** | `String` (_not_ `&str`) | `"string"` | `"hello"` etc. | -| **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ? ? ? ]"` | +| **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` | | **Object map** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `#{ "a": 1, "b": 2 }` | | **Timestamp** (implemented in the [`BasicTimePackage`](#packages)) | `std::time::Instant` | `"timestamp"` | _not supported_ | | **Dynamic value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ | @@ -576,6 +579,7 @@ A number of traits, under the `rhai::` module namespace, provide additional func | `RegisterDynamicFn` | Trait for registering functions returning [`Dynamic`] | `register_dynamic_fn` | | `RegisterResultFn` | Trait for registering fallible functions returning `Result<`_T_`, Box>` | `register_result_fn` | | `Func` | Trait for creating anonymous functions from script | `create_from_ast`, `create_from_script` | +| `ModuleResolver` | Trait implemented by module resolution services | `resolve` | Working with functions ---------------------- @@ -923,8 +927,44 @@ let result = engine.eval::("let a = new_ts(); a.xyz = 42; a.xyz")?; println!("Answer: {}", result); // prints 42 ``` -Needless to say, `register_type`, `register_type_with_name`, `register_get`, `register_set` and `register_get_set` -are not available when the [`no_object`] feature is turned on. +Indexers +-------- + +Custom types can also expose an _indexer_ by registering an indexer function. +A custom with an indexer function defined can use the bracket '`[]`' notation to get a property value +(but not update it - indexers are read-only). + +```rust +#[derive(Clone)] +struct TestStruct { + fields: Vec +} + +impl TestStruct { + fn get_field(&mut self, index: i64) -> i64 { + self.fields[index as usize] + } + + fn new() -> Self { + TestStruct { fields: vec![1, 2, 42, 4, 5] } + } +} + +let engine = Engine::new(); + +engine.register_type::(); + +engine.register_fn("new_ts", TestStruct::new); +engine.register_indexer(TestStruct::get_field); + +let result = engine.eval::("let a = new_ts(); a[2]")?; + +println!("Answer: {}", result); // prints 42 +``` + +Needless to say, `register_type`, `register_type_with_name`, `register_get`, `register_set`, `register_get_set` +and `register_indexer` are not available when the [`no_object`] feature is turned on. +`register_indexer` is also not available when the [`no_index`] feature is turned on. `Scope` - Initializing and maintaining state ------------------------------------------- @@ -1372,7 +1412,8 @@ y[2] == 3; y[3] == 4; (1 in y) == true; // use 'in' to test if an item exists in the array -(42 in y) == false; +(42 in y) == false; // 'in' uses the '==' operator (which users can override) + // to check if the target item exists in the array y[1] = 42; // array elements can be reassigned @@ -1494,7 +1535,7 @@ y.a == 42; y["baz!$@"] == 123.456; // access via index notation -"baz!$@" in y == true; // use 'in' to test if a property exists in the object map, prints true +"baz!$@" in y == true; // use 'in' to test if a property exists in the object map ("z" in y) == false; ts.obj = y; // object maps can be assigned completely (by value copy) @@ -1973,8 +2014,8 @@ When embedding Rhai into an application, it is usually necessary to trap `print` (for logging into a tracking log, for example). ```rust -// Any function or closure that takes an &str argument can be used to override -// print and debug +// Any function or closure that takes an '&str' argument can be used to override +// 'print' and 'debug' engine.on_print(|x| println!("hello: {}", x)); engine.on_debug(|x| println!("DEBUG: {}", x)); @@ -1997,6 +2038,98 @@ for entry in logbook.read().unwrap().iter() { } ``` +Using external modules +---------------------- + +[module]: #using-external-modules +[modules]: #using-external-modules + +Rhai allows organizing code (functions and variables) into _modules_. A module is a single script file +with `export` statements that _exports_ certain global variables and functions as contents of the module. + +Everything exported as part of a module is constant and read-only. + +### Importing modules + +A module can be _imported_ via the `import` statement, and its members accessed via '`::`' similar to C++. + +```rust +import "crypto" as crypto; // import the script file 'crypto.rhai' as a module + +crypto::encrypt(secret); // use functions defined under the module via '::' + +print(crypto::status); // module variables are constants + +crypto::hash::sha256(key); // sub-modules are also supported +``` + +`import` statements are _scoped_, meaning that they are only accessible inside the scope that they're imported. + +```rust +let mod = "crypto"; + +if secured { // new block scope + import mod as crypto; // import module (the path needs not be a constant string) + + crypto::encrypt(key); // use a function in the module +} // the module disappears at the end of the block scope + +crypto::encrypt(others); // <- this causes a run-time error because the 'crypto' module + // is no longer available! +``` + +### Creating custom modules from Rust + +To load a custom module into an [`Engine`], first create a `Module` type, add variables/functions into it, +then finally push it into a custom [`Scope`]. This has the equivalent effect of putting an `import` statement +at the beginning of any script run. + +```rust +use rhai::{Engine, Scope, Module, i64}; + +let mut engine = Engine::new(); +let mut scope = Scope::new(); + +let mut module = Module::new(); // new module +module.set_var("answer", 41_i64); // variable 'answer' under module +module.set_fn_1("inc", |x: i64| Ok(x+1)); // use the 'set_fn_XXX' API to add functions + +// Push the module into the custom scope under the name 'question' +// This is equivalent to 'import "..." as question;' +scope.push_module("question", module); + +// Use module-qualified variables +engine.eval_expression_with_scope::(&scope, "question::answer + 1")? == 42; + +// Call module-qualified functions +engine.eval_expression_with_scope::(&scope, "question::inc(question::answer)")? == 42; +``` + +### Module resolvers + +When encountering an `import` statement, Rhai attempts to _resolve_ the module based on the path string. +_Module Resolvers_ are service types that implement the [`ModuleResolver`](#traits) trait. +There are a number of standard resolvers built into Rhai, the default being the `FileModuleResolver` +which simply loads a script file based on the path (with `.rhai` extension attached) and execute it to form a module. + +Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace. + +| Module Resolver | Description | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `FileModuleResolver` | The default module resolution service, not available under the [`no_std`] feature. Loads a script file (based off the current directory) with `.rhai` extension.
The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function. | +| `StaticModuleResolver` | Loads modules that are statically added. This can be used when the [`no_std`] feature is turned on. | + +An [`Engine`]'s module resolver is set via a call to `set_module_resolver`: + +```rust +// Use the 'StaticModuleResolver' +let resolver = rhai::module_resolvers::StaticModuleResolver::new(); +engine.set_module_resolver(Some(resolver)); + +// Effectively disable 'import' statements by setting module resolver to 'None' +engine.set_module_resolver(None); +``` + Script optimization =================== diff --git a/examples/repl.rs b/examples/repl.rs index 4ae1c44f..a8763e28 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -1,4 +1,4 @@ -use rhai::{Dynamic, Engine, EvalAltResult, Scope, AST}; +use rhai::{Dynamic, Engine, EvalAltResult, Scope, AST, INT}; #[cfg(not(feature = "no_optimize"))] use rhai::OptimizationLevel; @@ -68,9 +68,9 @@ fn main() { let mut scope = Scope::new(); let mut input = String::new(); - let mut main_ast = AST::new(); - let mut ast_u = AST::new(); - let mut ast = AST::new(); + let mut main_ast: AST = Default::default(); + let mut ast_u: AST = Default::default(); + let mut ast: AST = Default::default(); println!("Rhai REPL tool"); println!("=============="); @@ -163,6 +163,7 @@ fn main() { } // Throw away all the statements, leaving only the functions + #[cfg(not(feature = "no_function"))] main_ast.retain_functions(); } } diff --git a/scripts/array.rhai b/scripts/array.rhai index 1d0768bd..cc6fc642 100644 --- a/scripts/array.rhai +++ b/scripts/array.rhai @@ -1,7 +1,9 @@ let x = [1, 2, 3]; -print(x[1]); // prints 2 +print("x[1] should be 2:"); +print(x[1]); x[1] = 5; -print(x[1]); // prints 5 +print("x[1] should be 5:"); +print(x[1]); diff --git a/scripts/assignment.rhai b/scripts/assignment.rhai index 9554a7e5..82ff7d92 100644 --- a/scripts/assignment.rhai +++ b/scripts/assignment.rhai @@ -1,2 +1,4 @@ +print("x should be 78:"); + let x = 78; print(x); diff --git a/scripts/comments.rhai b/scripts/comments.rhai index 645105bb..f5d24c93 100644 --- a/scripts/comments.rhai +++ b/scripts/comments.rhai @@ -3,7 +3,8 @@ let /* I am a spy in a variable declaration! */ x = 5; /* I am a simple - multi-line comment */ + multi-line + comment */ /* look /* at /* that, /* multi-line */ comments */ can be */ nested */ diff --git a/scripts/for1.rhai b/scripts/for1.rhai index 8eafedde..8fc6c95e 100644 --- a/scripts/for1.rhai +++ b/scripts/for1.rhai @@ -1,6 +1,6 @@ // This script runs for-loops -let arr = [1,2,3,4]; +let arr = [1, 2, 3, 4]; for a in arr { for b in [10, 20] { diff --git a/scripts/function_decl1.rhai b/scripts/function_decl1.rhai index 4e97e47c..908bcdec 100644 --- a/scripts/function_decl1.rhai +++ b/scripts/function_decl1.rhai @@ -4,4 +4,6 @@ fn bob() { return 3; } -print(bob()); // should print 3 +print("bob() should be 3:"); + +print(bob()); diff --git a/scripts/function_decl2.rhai b/scripts/function_decl2.rhai index 201dcea4..0d72743e 100644 --- a/scripts/function_decl2.rhai +++ b/scripts/function_decl2.rhai @@ -7,6 +7,10 @@ fn addme(a, b) { a + b; // notice that the last value is returned even if terminated by a semicolon } -print(addme(a, 4)); // should print 46 +print("addme(a, 4) should be 46:"); + +print(addme(a, 4)); + +print("a should still be 3:"); print(a); // should print 3 - 'a' is never changed diff --git a/scripts/function_decl3.rhai b/scripts/function_decl3.rhai index 339c4b61..4901f5c9 100644 --- a/scripts/function_decl3.rhai +++ b/scripts/function_decl3.rhai @@ -4,4 +4,6 @@ fn f(a, b, c, d, e, f) { a - b * c - d * e - f } -print(f(100, 5, 2, 9, 6, 32)); // should print 4 +print("f() call should be 4:"); + +print(f(100, 5, 2, 9, 6, 32)); diff --git a/scripts/if1.rhai b/scripts/if1.rhai index 6f414a78..cbfe2938 100644 --- a/scripts/if1.rhai +++ b/scripts/if1.rhai @@ -3,12 +3,12 @@ let b = 123; let x = 999; if a > b { - print("a > b"); + print("Oops! a > b"); } else if a < b { - print("a < b"); + print("a < b, x should be 0"); let x = 0; // this 'x' shadows the global 'x' print(x); // should print 0 } else { - print("a == b"); + print("Oops! a == b"); } \ No newline at end of file diff --git a/scripts/op1.rhai b/scripts/op1.rhai index 5351f999..bedfa563 100644 --- a/scripts/op1.rhai +++ b/scripts/op1.rhai @@ -1 +1,3 @@ -print(34 + 12); // should be 46 +print("The result should be 46:"); + +print(34 + 12); diff --git a/scripts/op2.rhai b/scripts/op2.rhai index fedbd0aa..e00a1b99 100644 --- a/scripts/op2.rhai +++ b/scripts/op2.rhai @@ -1,2 +1,4 @@ +print("The result should be 182:"); + let x = 12 + 34 * 5; -print(x); // should be 182 +print(x); diff --git a/scripts/op3.rhai b/scripts/op3.rhai index 7811dbac..aa7349a8 100644 --- a/scripts/op3.rhai +++ b/scripts/op3.rhai @@ -1,2 +1,4 @@ +print("The result should be 230:"); + let x = (12 + 34) * 5; -print(x); // should be 230 +print(x); diff --git a/scripts/primes.rhai b/scripts/primes.rhai index 22defcb4..668fa250 100644 --- a/scripts/primes.rhai +++ b/scripts/primes.rhai @@ -13,18 +13,17 @@ prime_mask[1] = false; let total_primes_found = 0; for p in range(2, MAX_NUMBER_TO_CHECK) { - if prime_mask[p] { - print(p); + if !prime_mask[p] { continue; } - total_primes_found += 1; - let i = 2 * p; + print(p); - while i < MAX_NUMBER_TO_CHECK { - prime_mask[i] = false; - i += p; - } + total_primes_found += 1; + + for i in range(2 * p, MAX_NUMBER_TO_CHECK, p) { + prime_mask[i] = false; + i += p; } } -print("Total " + total_primes_found + " primes."); +print("Total " + total_primes_found + " primes <= " + MAX_NUMBER_TO_CHECK); print("Run time = " + now.elapsed() + " seconds."); diff --git a/src/any.rs b/src/any.rs index d9aad2a6..16bb8ae7 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1,11 +1,19 @@ //! Helper module which defines the `Any` trait to to allow dynamic value handling. -use crate::engine::{Array, Map}; +#[cfg(not(feature = "no_module"))] +use crate::module::Module; + use crate::parser::INT; #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; +#[cfg(not(feature = "no_index"))] +use crate::engine::Array; + +#[cfg(not(feature = "no_object"))] +use crate::engine::Map; + use crate::stdlib::{ any::{type_name, Any, TypeId}, boxed::Box, @@ -119,18 +127,6 @@ impl dyn Variant { pub fn is(&self) -> bool { TypeId::of::() == self.type_id() } - - /// Get a reference of a specific type to the `Variant`. - /// Returns `None` if the cast fails. - pub fn downcast_ref(&self) -> Option<&T> { - Any::downcast_ref::(self.as_any()) - } - - /// Get a mutable reference of a specific type to the `Variant`. - /// Returns `None` if the cast fails. - pub fn downcast_mut(&mut self) -> Option<&mut T> { - Any::downcast_mut::(self.as_mut_any()) - } } /// A dynamic type containing any value. @@ -145,8 +141,12 @@ pub enum Union { Int(INT), #[cfg(not(feature = "no_float"))] Float(FLOAT), + #[cfg(not(feature = "no_index"))] Array(Box), + #[cfg(not(feature = "no_object"))] Map(Box), + #[cfg(not(feature = "no_module"))] + Module(Box), Variant(Box>), } @@ -175,8 +175,12 @@ impl Dynamic { Union::Int(_) => TypeId::of::(), #[cfg(not(feature = "no_float"))] Union::Float(_) => TypeId::of::(), + #[cfg(not(feature = "no_index"))] Union::Array(_) => TypeId::of::(), + #[cfg(not(feature = "no_object"))] Union::Map(_) => TypeId::of::(), + #[cfg(not(feature = "no_module"))] + Union::Module(_) => TypeId::of::(), Union::Variant(value) => (***value).type_id(), } } @@ -191,8 +195,12 @@ impl Dynamic { Union::Int(_) => type_name::(), #[cfg(not(feature = "no_float"))] Union::Float(_) => type_name::(), + #[cfg(not(feature = "no_index"))] Union::Array(_) => "array", + #[cfg(not(feature = "no_object"))] Union::Map(_) => "map", + #[cfg(not(feature = "no_module"))] + Union::Module(_) => "sub-scope", #[cfg(not(feature = "no_std"))] Union::Variant(value) if value.is::() => "timestamp", @@ -211,8 +219,15 @@ impl fmt::Display for Dynamic { Union::Int(value) => write!(f, "{}", value), #[cfg(not(feature = "no_float"))] Union::Float(value) => write!(f, "{}", value), + #[cfg(not(feature = "no_index"))] Union::Array(value) => write!(f, "{:?}", value), + #[cfg(not(feature = "no_object"))] Union::Map(value) => write!(f, "#{:?}", value), + #[cfg(not(feature = "no_module"))] + Union::Module(value) => write!(f, "{:?}", value), + + #[cfg(not(feature = "no_std"))] + Union::Variant(value) if value.is::() => write!(f, ""), Union::Variant(_) => write!(f, "?"), } } @@ -228,8 +243,15 @@ impl fmt::Debug for Dynamic { Union::Int(value) => write!(f, "{:?}", value), #[cfg(not(feature = "no_float"))] Union::Float(value) => write!(f, "{:?}", value), + #[cfg(not(feature = "no_index"))] Union::Array(value) => write!(f, "{:?}", value), + #[cfg(not(feature = "no_object"))] Union::Map(value) => write!(f, "#{:?}", value), + #[cfg(not(feature = "no_module"))] + Union::Module(value) => write!(f, "{:?}", value), + + #[cfg(not(feature = "no_std"))] + Union::Variant(value) if value.is::() => write!(f, ""), Union::Variant(_) => write!(f, ""), } } @@ -237,17 +259,21 @@ impl fmt::Debug for Dynamic { impl Clone for Dynamic { fn clone(&self) -> Self { - match &self.0 { - Union::Unit(value) => Self(Union::Unit(value.clone())), - Union::Bool(value) => Self(Union::Bool(value.clone())), - Union::Str(value) => Self(Union::Str(value.clone())), - Union::Char(value) => Self(Union::Char(value.clone())), - Union::Int(value) => Self(Union::Int(value.clone())), + match self.0 { + Union::Unit(value) => Self(Union::Unit(value)), + Union::Bool(value) => Self(Union::Bool(value)), + Union::Str(ref value) => Self(Union::Str(value.clone())), + Union::Char(value) => Self(Union::Char(value)), + Union::Int(value) => Self(Union::Int(value)), #[cfg(not(feature = "no_float"))] - Union::Float(value) => Self(Union::Float(value.clone())), - Union::Array(value) => Self(Union::Array(value.clone())), - Union::Map(value) => Self(Union::Map(value.clone())), - Union::Variant(value) => (***value).clone_into_dynamic(), + Union::Float(value) => Self(Union::Float(value)), + #[cfg(not(feature = "no_index"))] + Union::Array(ref value) => Self(Union::Array(value.clone())), + #[cfg(not(feature = "no_object"))] + Union::Map(ref value) => Self(Union::Map(value.clone())), + #[cfg(not(feature = "no_module"))] + Union::Module(ref value) => Self(Union::Module(value.clone())), + Union::Variant(ref value) => (***value).clone_into_dynamic(), } } } @@ -259,13 +285,13 @@ impl Default for Dynamic { } /// Cast a Boxed type into another type. -fn cast_box(item: Box) -> Result> { +fn cast_box(item: Box) -> Result, Box> { // Only allow casting to the exact same type if TypeId::of::() == TypeId::of::() { // SAFETY: just checked whether we are pointing to the correct type unsafe { let raw: *mut dyn Any = Box::into_raw(item as Box); - Ok(*Box::from_raw(raw as *mut T)) + Ok(Box::from_raw(raw as *mut T)) } } else { // Return the consumed item for chaining. @@ -300,7 +326,7 @@ impl Dynamic { /// assert_eq!(new_result.to_string(), "hello"); /// ``` pub fn from(value: T) -> Self { - let dyn_value = &value as &dyn Variant; + let dyn_value = &value as &dyn Any; if let Some(result) = dyn_value.downcast_ref::<()>().cloned().map(Union::Unit) { return Self(result); @@ -319,31 +345,33 @@ impl Dynamic { } } - let var = Box::new(value); + let mut var = Box::new(value); - Self( - cast_box::<_, Dynamic>(var) - .map(|x| x.0) - .or_else(|var| { - cast_box::<_, String>(var) - .map(Box::new) - .map(Union::Str) - .or_else(|var| { - cast_box::<_, Array>(var) - .map(Box::new) - .map(Union::Array) - .or_else(|var| { - cast_box::<_, Map>(var) - .map(Box::new) - .map(Union::Map) - .or_else(|var| -> Result { - Ok(Union::Variant(Box::new(var as Box))) - }) - }) - }) - }) - .unwrap(), - ) + var = match cast_box::<_, Dynamic>(var) { + Ok(d) => return *d, + Err(var) => var, + }; + var = match cast_box::<_, String>(var) { + Ok(s) => return Self(Union::Str(s)), + Err(var) => var, + }; + #[cfg(not(feature = "no_index"))] + { + var = match cast_box::<_, Array>(var) { + Ok(array) => return Self(Union::Array(array)), + Err(var) => var, + }; + } + + #[cfg(not(feature = "no_object"))] + { + var = match cast_box::<_, Map>(var) { + Ok(map) => return Self(Union::Map(map)), + Err(var) => var, + } + } + + Self(Union::Variant(Box::new(var))) } /// Get a copy of the `Dynamic` value as a specific type. @@ -362,20 +390,24 @@ impl Dynamic { /// ``` pub fn try_cast(self) -> Option { if TypeId::of::() == TypeId::of::() { - return cast_box::<_, T>(Box::new(self)).ok(); + return cast_box::<_, T>(Box::new(self)).ok().map(|v| *v); } match self.0 { - Union::Unit(ref value) => (value as &dyn Variant).downcast_ref::().cloned(), - Union::Bool(ref value) => (value as &dyn Variant).downcast_ref::().cloned(), - Union::Str(value) => cast_box::<_, T>(value).ok(), - Union::Char(ref value) => (value as &dyn Variant).downcast_ref::().cloned(), - Union::Int(ref value) => (value as &dyn Variant).downcast_ref::().cloned(), + Union::Unit(ref value) => (value as &dyn Any).downcast_ref::().cloned(), + Union::Bool(ref value) => (value as &dyn Any).downcast_ref::().cloned(), + Union::Str(value) => cast_box::<_, T>(value).ok().map(|v| *v), + Union::Char(ref value) => (value as &dyn Any).downcast_ref::().cloned(), + Union::Int(ref value) => (value as &dyn Any).downcast_ref::().cloned(), #[cfg(not(feature = "no_float"))] - Union::Float(ref value) => (value as &dyn Variant).downcast_ref::().cloned(), - Union::Array(value) => cast_box::<_, T>(value).ok(), - Union::Map(value) => cast_box::<_, T>(value).ok(), - Union::Variant(value) => value.as_ref().as_ref().downcast_ref::().cloned(), + Union::Float(ref value) => (value as &dyn Any).downcast_ref::().cloned(), + #[cfg(not(feature = "no_index"))] + Union::Array(value) => cast_box::<_, T>(value).ok().map(|v| *v), + #[cfg(not(feature = "no_object"))] + Union::Map(value) => cast_box::<_, T>(value).ok().map(|v| *v), + #[cfg(not(feature = "no_module"))] + Union::Module(value) => cast_box::<_, T>(value).ok().map(|v| *v), + Union::Variant(value) => value.as_any().downcast_ref::().cloned(), } } @@ -396,7 +428,28 @@ impl Dynamic { /// assert_eq!(x.cast::(), 42); /// ``` pub fn cast(self) -> T { - self.try_cast::().unwrap() + //self.try_cast::().unwrap() + + if TypeId::of::() == TypeId::of::() { + return *cast_box::<_, T>(Box::new(self)).unwrap(); + } + + match self.0 { + Union::Unit(ref value) => (value as &dyn Any).downcast_ref::().unwrap().clone(), + Union::Bool(ref value) => (value as &dyn Any).downcast_ref::().unwrap().clone(), + Union::Str(value) => *cast_box::<_, T>(value).unwrap(), + Union::Char(ref value) => (value as &dyn Any).downcast_ref::().unwrap().clone(), + Union::Int(ref value) => (value as &dyn Any).downcast_ref::().unwrap().clone(), + #[cfg(not(feature = "no_float"))] + Union::Float(ref value) => (value as &dyn Any).downcast_ref::().unwrap().clone(), + #[cfg(not(feature = "no_index"))] + Union::Array(value) => *cast_box::<_, T>(value).unwrap(), + #[cfg(not(feature = "no_object"))] + Union::Map(value) => *cast_box::<_, T>(value).unwrap(), + #[cfg(not(feature = "no_module"))] + Union::Module(value) => *cast_box::<_, T>(value).unwrap(), + Union::Variant(value) => value.as_any().downcast_ref::().unwrap().clone(), + } } /// Get a reference of a specific type to the `Dynamic`. @@ -404,20 +457,24 @@ impl Dynamic { /// Returns `None` if the cast fails. pub fn downcast_ref(&self) -> Option<&T> { if TypeId::of::() == TypeId::of::() { - return (self as &dyn Variant).downcast_ref::(); + return (self as &dyn Any).downcast_ref::(); } match &self.0 { - Union::Unit(value) => (value as &dyn Variant).downcast_ref::(), - Union::Bool(value) => (value as &dyn Variant).downcast_ref::(), - Union::Str(value) => (value.as_ref() as &dyn Variant).downcast_ref::(), - Union::Char(value) => (value as &dyn Variant).downcast_ref::(), - Union::Int(value) => (value as &dyn Variant).downcast_ref::(), + Union::Unit(value) => (value as &dyn Any).downcast_ref::(), + Union::Bool(value) => (value as &dyn Any).downcast_ref::(), + Union::Str(value) => (value.as_ref() as &dyn Any).downcast_ref::(), + Union::Char(value) => (value as &dyn Any).downcast_ref::(), + Union::Int(value) => (value as &dyn Any).downcast_ref::(), #[cfg(not(feature = "no_float"))] - Union::Float(value) => (value as &dyn Variant).downcast_ref::(), - Union::Array(value) => (value.as_ref() as &dyn Variant).downcast_ref::(), - Union::Map(value) => (value.as_ref() as &dyn Variant).downcast_ref::(), - Union::Variant(value) => value.as_ref().as_ref().downcast_ref::(), + Union::Float(value) => (value as &dyn Any).downcast_ref::(), + #[cfg(not(feature = "no_index"))] + Union::Array(value) => (value.as_ref() as &dyn Any).downcast_ref::(), + #[cfg(not(feature = "no_object"))] + Union::Map(value) => (value.as_ref() as &dyn Any).downcast_ref::(), + #[cfg(not(feature = "no_module"))] + Union::Module(value) => (value.as_ref() as &dyn Any).downcast_ref::(), + Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::(), } } @@ -426,20 +483,24 @@ impl Dynamic { /// Returns `None` if the cast fails. pub fn downcast_mut(&mut self) -> Option<&mut T> { if TypeId::of::() == TypeId::of::() { - return (self as &mut dyn Variant).downcast_mut::(); + return (self as &mut dyn Any).downcast_mut::(); } match &mut self.0 { - Union::Unit(value) => (value as &mut dyn Variant).downcast_mut::(), - Union::Bool(value) => (value as &mut dyn Variant).downcast_mut::(), - Union::Str(value) => (value.as_mut() as &mut dyn Variant).downcast_mut::(), - Union::Char(value) => (value as &mut dyn Variant).downcast_mut::(), - Union::Int(value) => (value as &mut dyn Variant).downcast_mut::(), + Union::Unit(value) => (value as &mut dyn Any).downcast_mut::(), + Union::Bool(value) => (value as &mut dyn Any).downcast_mut::(), + Union::Str(value) => (value.as_mut() as &mut dyn Any).downcast_mut::(), + Union::Char(value) => (value as &mut dyn Any).downcast_mut::(), + Union::Int(value) => (value as &mut dyn Any).downcast_mut::(), #[cfg(not(feature = "no_float"))] - Union::Float(value) => (value as &mut dyn Variant).downcast_mut::(), - Union::Array(value) => (value.as_mut() as &mut dyn Variant).downcast_mut::(), - Union::Map(value) => (value.as_mut() as &mut dyn Variant).downcast_mut::(), - Union::Variant(value) => value.as_mut().as_mut().downcast_mut::(), + Union::Float(value) => (value as &mut dyn Any).downcast_mut::(), + #[cfg(not(feature = "no_index"))] + Union::Array(value) => (value.as_mut() as &mut dyn Any).downcast_mut::(), + #[cfg(not(feature = "no_object"))] + Union::Map(value) => (value.as_mut() as &mut dyn Any).downcast_mut::(), + #[cfg(not(feature = "no_module"))] + Union::Module(value) => (value.as_mut() as &mut dyn Any).downcast_mut::(), + Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::(), } } @@ -520,6 +581,7 @@ impl From for Dynamic { Self(Union::Str(Box::new(value))) } } +#[cfg(not(feature = "no_index"))] impl From> for Dynamic { fn from(value: Vec) -> Self { Self(Union::Array(Box::new( @@ -527,6 +589,7 @@ impl From> for Dynamic { ))) } } +#[cfg(not(feature = "no_object"))] impl From> for Dynamic { fn from(value: HashMap) -> Self { Self(Union::Map(Box::new( diff --git a/src/api.rs b/src/api.rs index 9377bc68..d0193549 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,7 +1,7 @@ //! Module that defines the extern API of `Engine`. use crate::any::{Dynamic, Variant}; -use crate::engine::{make_getter, make_setter, Engine, Map, State}; +use crate::engine::{make_getter, make_setter, Engine, State, FUNC_INDEXER}; use crate::error::ParseError; use crate::fn_call::FuncArgs; use crate::fn_register::RegisterFn; @@ -11,10 +11,14 @@ use crate::result::EvalAltResult; use crate::scope::Scope; use crate::token::{lex, Position}; +#[cfg(not(feature = "no_object"))] +use crate::engine::Map; + use crate::stdlib::{ any::{type_name, TypeId}, boxed::Box, collections::HashMap, + mem, string::{String, ToString}, vec::Vec, }; @@ -42,6 +46,16 @@ pub trait ObjectSetCallback: Fn(&mut T, U) + 'static {} #[cfg(not(feature = "sync"))] impl ObjectSetCallback for F {} +#[cfg(feature = "sync")] +pub trait ObjectIndexerCallback: Fn(&mut T, X) -> U + Send + Sync + 'static {} +#[cfg(feature = "sync")] +impl U + Send + Sync + 'static, T, X, U> ObjectIndexerCallback for F {} + +#[cfg(not(feature = "sync"))] +pub trait ObjectIndexerCallback: Fn(&mut T, X) -> U + 'static {} +#[cfg(not(feature = "sync"))] +impl U + 'static, T, X, U> ObjectIndexerCallback for F {} + #[cfg(feature = "sync")] pub trait IteratorCallback: Fn(Dynamic) -> Box> + Send + Sync + 'static @@ -299,6 +313,54 @@ impl Engine { self.register_set(name, set_fn); } + /// Register an indexer function for a registered type with the `Engine`. + /// + /// The function signature must start with `&mut self` and not `&self`. + /// + /// # Example + /// + /// ``` + /// #[derive(Clone)] + /// struct TestStruct { + /// fields: Vec + /// } + /// + /// impl TestStruct { + /// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } } + /// + /// // Even a getter must start with `&mut self` and not `&self`. + /// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// use rhai::{Engine, RegisterFn}; + /// + /// let mut engine = Engine::new(); + /// + /// // Register the custom type. + /// engine.register_type::(); + /// + /// engine.register_fn("new_ts", TestStruct::new); + /// + /// // Register an indexer. + /// engine.register_indexer(TestStruct::get_field); + /// + /// assert_eq!(engine.eval::("let a = new_ts(); a[2]")?, 3); + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_object"))] + #[cfg(not(feature = "no_index"))] + pub fn register_indexer(&mut self, callback: F) + where + T: Variant + Clone, + U: Variant + Clone, + X: Variant + Clone, + F: ObjectIndexerCallback, + { + self.register_fn(FUNC_INDEXER, callback); + } + /// Compile a string into an `AST`, which can be used later for evaluation. /// /// # Example @@ -378,13 +440,23 @@ impl Engine { /// Read the contents of a file into a string. #[cfg(not(feature = "no_std"))] fn read_file(path: PathBuf) -> Result> { - let mut f = File::open(path.clone()) - .map_err(|err| Box::new(EvalAltResult::ErrorReadingScriptFile(path.clone(), err)))?; + let mut f = File::open(path.clone()).map_err(|err| { + Box::new(EvalAltResult::ErrorReadingScriptFile( + path.clone(), + Position::none(), + err, + )) + })?; let mut contents = String::new(); - f.read_to_string(&mut contents) - .map_err(|err| Box::new(EvalAltResult::ErrorReadingScriptFile(path.clone(), err)))?; + f.read_to_string(&mut contents).map_err(|err| { + Box::new(EvalAltResult::ErrorReadingScriptFile( + path.clone(), + Position::none(), + err, + )) + })?; Ok(contents) } @@ -797,10 +869,10 @@ impl Engine { ) -> Result> { let mut state = State::new(); - ast.0 + ast.statements() .iter() .try_fold(().into(), |_, stmt| { - self.eval_stmt(scope, &mut state, ast.1.as_ref(), stmt, 0) + self.eval_stmt(scope, &mut state, ast.fn_lib(), stmt, 0) }) .or_else(|err| match *err { EvalAltResult::Return(out, _) => Ok(out), @@ -862,10 +934,10 @@ impl Engine { ) -> Result<(), Box> { let mut state = State::new(); - ast.0 + ast.statements() .iter() .try_fold(().into(), |_, stmt| { - self.eval_stmt(scope, &mut state, ast.1.as_ref(), stmt, 0) + self.eval_stmt(scope, &mut state, ast.fn_lib(), stmt, 0) }) .map_or_else( |err| match *err { @@ -921,7 +993,7 @@ impl Engine { ) -> Result> { let mut arg_values = args.into_vec(); let mut args: Vec<_> = arg_values.iter_mut().collect(); - let fn_lib = ast.1.as_ref(); + let fn_lib = ast.fn_lib(); let pos = Position::none(); let fn_def = fn_lib @@ -955,15 +1027,17 @@ impl Engine { pub fn optimize_ast( &self, scope: &Scope, - ast: AST, + mut ast: AST, optimization_level: OptimizationLevel, ) -> AST { let fn_lib = ast - .1 + .fn_lib() .iter() .map(|(_, fn_def)| fn_def.as_ref().clone()) .collect(); - optimize_into_ast(self, scope, ast.0, fn_lib, optimization_level) + + let stmt = mem::take(ast.statements_mut()); + optimize_into_ast(self, scope, stmt, fn_lib, optimization_level) } /// Override default action of `print` (print to stdout using `println!`) diff --git a/src/engine.rs b/src/engine.rs index c24b924c..b35419cb 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -5,19 +5,23 @@ use crate::calc_fn_hash; use crate::error::ParseErrorType; use crate::optimize::OptimizationLevel; use crate::packages::{CorePackage, Package, PackageLibrary, StandardPackage}; -use crate::parser::{Expr, FnDef, ReturnType, Stmt}; +use crate::parser::{Expr, FnDef, ModuleRef, ReturnType, Stmt, AST}; use crate::result::EvalAltResult; use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::token::Position; +use crate::utils::{calc_fn_def, StaticVec}; + +#[cfg(not(feature = "no_module"))] +use crate::module::{resolvers, Module, ModuleResolver}; use crate::stdlib::{ any::TypeId, boxed::Box, collections::HashMap, format, - hash::{Hash, Hasher}, iter::once, mem, + num::NonZeroUsize, ops::{Deref, DerefMut}, rc::Rc, string::{String, ToString}, @@ -25,20 +29,16 @@ use crate::stdlib::{ vec::Vec, }; -#[cfg(not(feature = "no_std"))] -use crate::stdlib::collections::hash_map::DefaultHasher; - -#[cfg(feature = "no_std")] -use ahash::AHasher; - /// An dynamic array of `Dynamic` values. /// /// Not available under the `no_index` feature. +#[cfg(not(feature = "no_index"))] pub type Array = Vec; /// An dynamic hash map of `Dynamic` values with `String` keys. /// /// Not available under the `no_object` feature. +#[cfg(not(feature = "no_object"))] pub type Map = HashMap; pub type FnCallArgs<'a> = [&'a mut Dynamic]; @@ -74,6 +74,7 @@ pub const KEYWORD_EVAL: &str = "eval"; pub const FUNC_TO_STRING: &str = "to_string"; pub const FUNC_GETTER: &str = "get$"; pub const FUNC_SETTER: &str = "set$"; +pub const FUNC_INDEXER: &str = "$index$"; /// A type that encapsulates a mutation target for an expression with side effects. enum Target<'a> { @@ -120,7 +121,7 @@ impl Target<'_> { chars.iter().for_each(|&ch| s.push(ch)); } } - _ => panic!("should be String"), + _ => unreachable!(), }, } @@ -139,62 +140,8 @@ impl> From for Target<'_> { } } -/// A type to hold a number of `Dynamic` values in static storage for speed, -/// and any spill-overs in a `Vec`. -struct StaticVec { - /// Total number of values held. - len: usize, - /// Static storage. 4 slots should be enough for most cases - i.e. four levels of indirection. - list: [T; 4], - /// Dynamic storage. For spill-overs. - more: Vec, -} - -impl StaticVec { - /// Create a new `StaticVec`. - pub fn new() -> Self { - Self { - len: 0, - list: [ - Default::default(), - Default::default(), - Default::default(), - Default::default(), - ], - more: Vec::new(), - } - } - /// Push a new value to the end of this `StaticVec`. - pub fn push>(&mut self, value: X) { - if self.len >= self.list.len() { - self.more.push(value.into()); - } else { - self.list[self.len] = value.into(); - } - self.len += 1; - } - /// Pop a value from the end of this `StaticVec`. - /// - /// # Panics - /// - /// Panics if the `StaticVec` is empty. - pub fn pop(&mut self) -> T { - let result = if self.len <= 0 { - panic!("nothing to pop!") - } else if self.len <= self.list.len() { - mem::replace(self.list.get_mut(self.len - 1).unwrap(), Default::default()) - } else { - self.more.pop().unwrap() - }; - - self.len -= 1; - - result - } -} - /// A type that holds all the current states of the Engine. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct State { /// Normally, access to variables are parsed with a relative offset into the scope to avoid a lookup. /// In some situation, e.g. after running an `eval` statement, subsequent offsets may become mis-aligned. @@ -217,7 +164,7 @@ impl State { /// and number of parameters are considered equivalent. /// /// The key of the `HashMap` is a `u64` hash calculated by the function `calc_fn_def`. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct FunctionsLib( #[cfg(feature = "sync")] HashMap>, #[cfg(not(feature = "sync"))] HashMap>, @@ -226,7 +173,7 @@ pub struct FunctionsLib( impl FunctionsLib { /// Create a new `FunctionsLib`. pub fn new() -> Self { - FunctionsLib(HashMap::new()) + Default::default() } /// Create a new `FunctionsLib` from a collection of `FnDef`. pub fn from_vec(vec: Vec) -> Self { @@ -317,6 +264,11 @@ pub struct Engine { /// A hashmap containing all iterators known to the engine. pub(crate) type_iterators: HashMap>, + + /// A module resolution service. + #[cfg(not(feature = "no_module"))] + pub(crate) module_resolver: Option>, + /// A hashmap mapping type names to pretty-print names. pub(crate) type_names: HashMap, @@ -347,10 +299,18 @@ impl Default for Engine { fn default() -> Self { // Create the new scripting Engine let mut engine = Self { - packages: Vec::new(), + packages: Default::default(), functions: HashMap::with_capacity(FUNCTIONS_COUNT), - type_iterators: HashMap::new(), - type_names: HashMap::new(), + type_iterators: Default::default(), + + #[cfg(not(feature = "no_module"))] + #[cfg(not(feature = "no_std"))] + module_resolver: Some(Box::new(resolvers::FileModuleResolver::new())), + #[cfg(not(feature = "no_module"))] + #[cfg(feature = "no_std")] + module_resolver: None, + + type_names: Default::default(), // default print/debug implementations print: Box::new(default_print), @@ -423,48 +383,53 @@ fn extract_prop_from_setter(fn_name: &str) -> Option<&str> { } } -/// Calculate a `u64` hash key from a function name and parameter types. -/// -/// Parameter types are passed in via `TypeId` values from an iterator -/// which can come from any source. -pub fn calc_fn_spec(fn_name: &str, params: impl Iterator) -> u64 { - #[cfg(feature = "no_std")] - let mut s: AHasher = Default::default(); - #[cfg(not(feature = "no_std"))] - let mut s = DefaultHasher::new(); - - s.write(fn_name.as_bytes()); - params.for_each(|t| t.hash(&mut s)); - s.finish() -} - -/// Calculate a `u64` hash key from a function name and number of parameters (without regard to types). -pub(crate) fn calc_fn_def(fn_name: &str, params: usize) -> u64 { - #[cfg(feature = "no_std")] - let mut s: AHasher = Default::default(); - #[cfg(not(feature = "no_std"))] - let mut s = DefaultHasher::new(); - - s.write(fn_name.as_bytes()); - s.write_usize(params); - s.finish() -} - /// Print/debug to stdout fn default_print(s: &str) { #[cfg(not(feature = "no_std"))] println!("{}", s); } -/// Search for a variable within the scope, returning its value and index inside the Scope +/// Search for a variable within the scope fn search_scope<'a>( scope: &'a mut Scope, name: &str, - begin: Position, + modules: &ModuleRef, + index: Option, + pos: Position, ) -> Result<(&'a mut Dynamic, ScopeEntryType), Box> { - let (index, _) = scope - .get(name) - .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), begin)))?; + #[cfg(not(feature = "no_module"))] + { + if let Some(modules) = modules { + let (id, root_pos) = modules.get(0); // First module + + let module = if let Some(index) = index { + scope + .get_mut(scope.len() - index.get()) + .0 + .downcast_mut::() + .unwrap() + } else { + scope.find_module(id).ok_or_else(|| { + Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)) + })? + }; + + return Ok(( + module.get_qualified_var_mut(name, modules.as_ref(), pos)?, + // Module variables are constant + ScopeEntryType::Constant, + )); + } + } + + let index = if let Some(index) = index { + scope.len() - index.get() + } else { + scope + .get_index(name) + .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))? + .0 + }; Ok(scope.get_mut(index)) } @@ -479,10 +444,14 @@ impl Engine { /// Use the `load_package` method to load packages of functions. pub fn new_raw() -> Self { Self { - packages: Vec::new(), + packages: Default::default(), functions: HashMap::with_capacity(FUNCTIONS_COUNT / 2), - type_iterators: HashMap::new(), - type_names: HashMap::new(), + type_iterators: Default::default(), + + #[cfg(not(feature = "no_module"))] + module_resolver: None, + + type_names: Default::default(), print: Box::new(|_| {}), debug: Box::new(|_| {}), @@ -510,7 +479,7 @@ impl Engine { self.packages.insert(0, package); } - /// Control whether and how the `Engine` will optimize an AST after compilation + /// Control whether and how the `Engine` will optimize an AST after compilation. /// /// Not available under the `no_optimize` feature. #[cfg(not(feature = "no_optimize"))] @@ -524,7 +493,15 @@ impl Engine { self.max_call_stack_depth = levels } - /// Universal method for calling functions either registered with the `Engine` or written in Rhai + /// Set the module resolution service used by the `Engine`. + /// + /// Not available under the `no_module` feature. + #[cfg(not(feature = "no_module"))] + pub fn set_module_resolver(&mut self, resolver: Option) { + self.module_resolver = resolver.map(|f| Box::new(f) as Box); + } + + /// Universal method for calling functions either registered with the `Engine` or written in Rhai. pub(crate) fn call_fn_raw( &self, scope: Option<&mut Scope>, @@ -577,6 +554,11 @@ impl Engine { }); } + // Return default value (if any) + if let Some(val) = def_val { + return Ok(val.clone()); + } + // Getter function not found? if let Some(prop) = extract_prop_from_getter(fn_name) { return Err(Box::new(EvalAltResult::ErrorDotExpr( @@ -593,17 +575,20 @@ impl Engine { ))); } - // Return default value (if any) - if let Some(val) = def_val { - return Ok(val.clone()); - } - - // Raise error let types_list: Vec<_> = args .iter() .map(|name| self.map_type_name(name.type_name())) .collect(); + // Getter function not found? + if fn_name == FUNC_INDEXER { + return Err(Box::new(EvalAltResult::ErrorFunctionNotFound( + format!("[]({})", types_list.join(", ")), + pos, + ))); + } + + // Raise error Err(Box::new(EvalAltResult::ErrorFunctionNotFound( format!("{} ({})", fn_name, types_list.join(", ")), pos, @@ -628,11 +613,13 @@ impl Engine { // Put arguments into scope as variables - variable name is copied scope.extend( - // TODO - avoid copying variable name fn_def .params .iter() - .zip(args.into_iter().map(|v| v.clone())) + .zip( + // Actually consume the arguments instead of cloning them + args.into_iter().map(|v| mem::take(*v)), + ) .map(|(name, value)| (name.clone(), ScopeEntryType::Normal, value)), ); @@ -659,7 +646,10 @@ impl Engine { fn_def .params .iter() - .zip(args.into_iter().map(|v| v.clone())) + .zip( + // Actually consume the arguments instead of cloning them + args.into_iter().map(|v| mem::take(*v)), + ) .map(|(name, value)| (name, ScopeEntryType::Normal, value)), ); @@ -692,7 +682,7 @@ impl Engine { &self, fn_lib: &FunctionsLib, fn_name: &str, - args: &mut [&mut Dynamic], + args: &mut FnCallArgs, def_val: Option<&Dynamic>, pos: Position, level: usize, @@ -710,7 +700,6 @@ impl Engine { pos, ))) } - // Normal method call _ => self.call_fn_raw(None, fn_lib, fn_name, args, def_val, pos, level), } @@ -737,20 +726,14 @@ impl Engine { )?; // If new functions are defined within the eval string, it is an error - if ast.1.len() > 0 { + if ast.fn_lib().len() > 0 { return Err(Box::new(EvalAltResult::ErrorParsing( ParseErrorType::WrongFnDefinition.into_err(pos), ))); } - #[cfg(feature = "sync")] - { - ast.1 = Arc::new(fn_lib.clone()); - } - #[cfg(not(feature = "sync"))] - { - ast.1 = Rc::new(fn_lib.clone()); - } + let statements = mem::take(ast.statements_mut()); + let ast = AST::new(statements, fn_lib.clone()); // Evaluate the AST self.eval_ast_with_scope_raw(scope, &ast) @@ -787,48 +770,52 @@ impl Engine { Expr::Index(idx, idx_rhs, pos) => { let is_index = matches!(rhs, Expr::Index(_,_,_)); - let indexed_val = self.get_indexed_mut(obj, idx_val, idx.position(), op_pos, false)?; + let indexed_val = self.get_indexed_mut(fn_lib, obj, idx_val, idx.position(), op_pos, false)?; self.eval_dot_index_chain_helper( fn_lib, indexed_val, idx_rhs.as_ref(), idx_values, is_index, *pos, level, new_val ) } // xxx[rhs] = new_val _ if new_val.is_some() => { - let mut indexed_val = self.get_indexed_mut(obj, idx_val, rhs.position(), op_pos, true)?; + let mut indexed_val = self.get_indexed_mut(fn_lib, obj, idx_val, rhs.position(), op_pos, true)?; indexed_val.set_value(new_val.unwrap(), rhs.position())?; Ok((Default::default(), true)) } // xxx[rhs] _ => self - .get_indexed_mut(obj, idx_val, rhs.position(), op_pos, false) + .get_indexed_mut(fn_lib, obj, idx_val, rhs.position(), op_pos, false) .map(|v| (v.clone_into_dynamic(), false)) } } else { match rhs { // xxx.fn_name(arg_expr_list) - Expr::FnCall(fn_name, _, def_val, pos) => { + Expr::FnCall(fn_name, None,_, def_val, pos) => { let mut args: Vec<_> = once(obj) - .chain(idx_val.downcast_mut::().unwrap().iter_mut()) + .chain(idx_val.downcast_mut::>().unwrap().iter_mut()) .collect(); let def_val = def_val.as_deref(); // A function call is assumed to have side effects, so the value is changed // TODO - Remove assumption of side effects by checking whether the first parameter is &mut self.exec_fn_call(fn_lib, fn_name, &mut args, def_val, *pos, 0).map(|v| (v, true)) } + // xxx.module::fn_name(...) - syntax error + Expr::FnCall(_,_,_,_,_) => unreachable!(), // {xxx:map}.id = ??? + #[cfg(not(feature = "no_object"))] Expr::Property(id, pos) if obj.is::() && new_val.is_some() => { let mut indexed_val = - self.get_indexed_mut(obj, id.to_string().into(), *pos, op_pos, true)?; + self.get_indexed_mut(fn_lib, obj, id.to_string().into(), *pos, op_pos, true)?; indexed_val.set_value(new_val.unwrap(), rhs.position())?; Ok((Default::default(), true)) } // {xxx:map}.id + #[cfg(not(feature = "no_object"))] Expr::Property(id, pos) if obj.is::() => { let indexed_val = - self.get_indexed_mut(obj, id.to_string().into(), *pos, op_pos, false)?; + self.get_indexed_mut(fn_lib, obj, id.to_string().into(), *pos, op_pos, false)?; Ok((indexed_val.clone_into_dynamic(), false)) } - // xxx.id = ??? + // xxx.id = ??? a Expr::Property(id, pos) if new_val.is_some() => { let fn_name = make_setter(id); let mut args = [obj, new_val.as_mut().unwrap()]; @@ -840,6 +827,7 @@ impl Engine { let mut args = [obj]; self.exec_fn_call(fn_lib, &fn_name, &mut args, None, *pos, 0).map(|v| (v, false)) } + #[cfg(not(feature = "no_object"))] // {xxx:map}.idx_lhs[idx_expr] Expr::Index(dot_lhs, dot_rhs, pos) | // {xxx:map}.dot_lhs.rhs @@ -847,7 +835,7 @@ impl Engine { let is_index = matches!(rhs, Expr::Index(_,_,_)); let indexed_val = if let Expr::Property(id, pos) = dot_lhs.as_ref() { - self.get_indexed_mut(obj, id.to_string().into(), *pos, op_pos, false)? + self.get_indexed_mut(fn_lib, obj, id.to_string().into(), *pos, op_pos, false)? } else { // Syntax error return Err(Box::new(EvalAltResult::ErrorDotExpr( @@ -884,8 +872,13 @@ impl Engine { if may_be_changed { if let Expr::Property(id, pos) = dot_lhs.as_ref() { let fn_name = make_setter(id); + // Re-use args because the first &mut parameter will not be consumed args[1] = indexed_val; - self.exec_fn_call(fn_lib, &fn_name, &mut args, None, *pos, 0)?; + self.exec_fn_call(fn_lib, &fn_name, &mut args, None, *pos, 0).or_else(|err| match *err { + // If there is no setter, no need to feed it back because the property is read-only + EvalAltResult::ErrorDotExpr(_,_) => Ok(Default::default()), + err => Err(Box::new(err)) + })?; } } @@ -919,21 +912,20 @@ impl Engine { match dot_lhs { // id.??? or id[???] - Expr::Variable(id, index, pos) => { - let (target, typ) = match index { - Some(i) if !state.always_search => scope.get_mut(scope.len() - i.get()), - _ => search_scope(scope, id, *pos)?, - }; + Expr::Variable(id, modules, index, pos) => { + let index = if state.always_search { None } else { *index }; + let (target, typ) = search_scope(scope, id, modules, index, *pos)?; // Constants cannot be modified match typ { + ScopeEntryType::Module => unreachable!(), ScopeEntryType::Constant if new_val.is_some() => { return Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( id.to_string(), *pos, ))); } - _ => (), + ScopeEntryType::Constant | ScopeEntryType::Normal => (), } let this_ptr = target.into(); @@ -976,14 +968,18 @@ impl Engine { level: usize, ) -> Result<(), Box> { match expr { - Expr::FnCall(_, arg_exprs, _, _) => { + Expr::FnCall(_, None, arg_exprs, _, _) => { let arg_values = arg_exprs .iter() .map(|arg_expr| self.eval_expr(scope, state, fn_lib, arg_expr, level)) .collect::, _>>()?; - idx_values.push(arg_values) + #[cfg(not(feature = "no_index"))] + idx_values.push(arg_values); + #[cfg(feature = "no_index")] + idx_values.push(Dynamic::from(arg_values)); } + Expr::FnCall(_, _, _, _, _) => unreachable!(), Expr::Property(_, _) => idx_values.push(()), // Store a placeholder - no need to copy the property name Expr::Index(lhs, rhs, _) | Expr::Dot(lhs, rhs, _) => { // Evaluate in left-to-right order @@ -1006,8 +1002,9 @@ impl Engine { /// Get the value at the indexed position of a base type fn get_indexed_mut<'a>( &self, + fn_lib: &FunctionsLib, val: &'a mut Dynamic, - idx: Dynamic, + mut idx: Dynamic, idx_pos: Position, op_pos: Position, create: bool, @@ -1015,6 +1012,7 @@ impl Engine { let type_name = self.map_type_name(val.type_name()); match val { + #[cfg(not(feature = "no_index"))] Dynamic(Union::Array(arr)) => { // val_array[idx] let index = idx @@ -1036,6 +1034,7 @@ impl Engine { } } + #[cfg(not(feature = "no_object"))] Dynamic(Union::Map(map)) => { // val_map[idx] let index = idx @@ -1051,6 +1050,7 @@ impl Engine { }) } + #[cfg(not(feature = "no_index"))] Dynamic(Union::Str(s)) => { // val_string[idx] let index = idx @@ -1080,11 +1080,18 @@ impl Engine { } } - // Error - cannot be indexed - _ => Err(Box::new(EvalAltResult::ErrorIndexingType( - type_name.to_string(), - op_pos, - ))), + _ => { + let args = &mut [val, &mut idx]; + self.exec_fn_call(fn_lib, FUNC_INDEXER, args, None, op_pos, 0) + .map(|v| v.into()) + .map_err(|_| { + Box::new(EvalAltResult::ErrorIndexingType( + // Error - cannot be indexed + type_name.to_string(), + op_pos, + )) + }) + } } } @@ -1102,6 +1109,7 @@ impl Engine { let rhs_value = self.eval_expr(scope, state, fn_lib, rhs, level)?; match rhs_value { + #[cfg(not(feature = "no_index"))] Dynamic(Union::Array(mut rhs_value)) => { let def_value = false.into(); @@ -1109,9 +1117,10 @@ impl Engine { for value in rhs_value.iter_mut() { let args = &mut [&mut lhs_value, value]; let def_value = Some(&def_value); + let pos = rhs.position(); if self - .call_fn_raw(None, fn_lib, "==", args, def_value, rhs.position(), level)? + .call_fn_raw(None, fn_lib, "==", args, def_value, pos, level)? .as_bool() .unwrap_or(false) { @@ -1121,6 +1130,7 @@ impl Engine { Ok(false.into()) } + #[cfg(not(feature = "no_object"))] Dynamic(Union::Map(rhs_value)) => match lhs_value { // Only allows String or char Dynamic(Union::Str(s)) => Ok(rhs_value.contains_key(s.as_ref()).into()), @@ -1152,11 +1162,12 @@ impl Engine { Expr::FloatConstant(f, _) => Ok((*f).into()), Expr::StringConstant(s, _) => Ok(s.to_string().into()), Expr::CharConstant(c, _) => Ok((*c).into()), - Expr::Variable(_, Some(index), _) if !state.always_search => { - Ok(scope.get_mut(scope.len() - index.get()).0.clone()) + Expr::Variable(id, modules, index, pos) => { + let index = if state.always_search { None } else { *index }; + let val = search_scope(scope, id, modules, index, *pos)?; + Ok(val.0.clone()) } - Expr::Variable(id, _, pos) => search_scope(scope, id, *pos).map(|(v, _)| v.clone()), - Expr::Property(_, _) => panic!("unexpected property."), + Expr::Property(_, _) => unreachable!(), // Statement block Expr::Stmt(stmt, _) => self.eval_stmt(scope, state, fn_lib, stmt, level), @@ -1167,21 +1178,20 @@ impl Engine { match lhs.as_ref() { // name = rhs - Expr::Variable(name, _, pos) => match scope.get(name) { - None => { - return Err(Box::new(EvalAltResult::ErrorVariableNotFound( - name.to_string(), - *pos, - ))) + Expr::Variable(name, modules, index, pos) => { + let index = if state.always_search { None } else { *index }; + match search_scope(scope, name, modules, index, *pos)? { + (_, ScopeEntryType::Constant) => Err(Box::new( + EvalAltResult::ErrorAssignmentToConstant(name.to_string(), *op_pos), + )), + (value_ptr, ScopeEntryType::Normal) => { + *value_ptr = rhs_val; + Ok(Default::default()) + } + // End variable cannot be a module + (_, ScopeEntryType::Module) => unreachable!(), } - Some((_, ScopeEntryType::Constant)) => Err(Box::new( - EvalAltResult::ErrorAssignmentToConstant(name.to_string(), *op_pos), - )), - Some((index, ScopeEntryType::Normal)) => { - *scope.get_mut(index).0 = rhs_val; - Ok(Default::default()) - } - }, + } // idx_lhs[idx_expr] = rhs #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, idx_expr, op_pos) => { @@ -1227,7 +1237,7 @@ impl Engine { #[cfg(not(feature = "no_index"))] Expr::Array(contents, _) => Ok(Dynamic(Union::Array(Box::new( contents - .into_iter() + .iter() .map(|item| self.eval_expr(scope, state, fn_lib, item, level)) .collect::, _>>()?, )))), @@ -1235,15 +1245,16 @@ impl Engine { #[cfg(not(feature = "no_object"))] Expr::Map(contents, _) => Ok(Dynamic(Union::Map(Box::new( contents - .into_iter() + .iter() .map(|(key, expr, _)| { - self.eval_expr(scope, state, fn_lib, &expr, level) + self.eval_expr(scope, state, fn_lib, expr, level) .map(|val| (key.clone(), val)) }) .collect::, _>>()?, )))), - Expr::FnCall(fn_name, arg_exprs, def_val, pos) => { + // Normal function call + Expr::FnCall(fn_name, None, arg_exprs, def_val, pos) => { let mut arg_values = arg_exprs .iter() .map(|expr| self.eval_expr(scope, state, fn_lib, expr, level)) @@ -1251,11 +1262,11 @@ impl Engine { let mut args: Vec<_> = arg_values.iter_mut().collect(); - // eval - only in function call style if fn_name.as_ref() == KEYWORD_EVAL && args.len() == 1 && !self.has_override(fn_lib, KEYWORD_EVAL) { + // eval - only in function call style let prev_len = scope.len(); // Evaluate the text string as a script @@ -1268,11 +1279,45 @@ impl Engine { state.always_search = true; } - return result; + result + } else { + // Normal function call - except for eval (handled above) + let def_value = def_val.as_deref(); + self.exec_fn_call(fn_lib, fn_name, &mut args, def_value, *pos, level) } + } - // Normal function call - except for eval (handled above) - self.exec_fn_call(fn_lib, fn_name, &mut args, def_val.as_deref(), *pos, level) + // Module-qualified function call + #[cfg(not(feature = "no_module"))] + Expr::FnCall(fn_name, Some(modules), arg_exprs, def_val, pos) => { + let modules = modules.as_ref(); + + let mut arg_values = arg_exprs + .iter() + .map(|expr| self.eval_expr(scope, state, fn_lib, expr, level)) + .collect::, _>>()?; + + let mut args: Vec<_> = arg_values.iter_mut().collect(); + + let (id, root_pos) = modules.get(0); // First module + + let module = scope.find_module(id).ok_or_else(|| { + Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)) + })?; + + // First search in script-defined functions (can override built-in) + if let Some(fn_def) = module.get_qualified_fn_lib(fn_name, args.len(), modules)? { + self.call_fn_from_lib(None, fn_lib, fn_def, &mut args, *pos, level) + } else { + // Then search in Rust functions + let hash = calc_fn_hash(fn_name, args.iter().map(|a| a.type_id())); + + match module.get_qualified_fn(fn_name, hash, modules, *pos) { + Ok(func) => func(&mut args, *pos), + Err(_) if def_val.is_some() => Ok(def_val.as_deref().unwrap().clone()), + Err(err) => Err(err), + } + } } Expr::In(lhs, rhs, _) => { @@ -1313,7 +1358,7 @@ impl Engine { Expr::False(_) => Ok(false.into()), Expr::Unit(_) => Ok(().into()), - _ => panic!("should not appear: {:?}", expr), + _ => unreachable!(), } } @@ -1498,7 +1543,38 @@ impl Engine { Ok(Default::default()) } - Stmt::Const(_, _, _) => panic!("constant expression not constant!"), + // Const expression not constant + Stmt::Const(_, _, _) => unreachable!(), + + // Import statement + Stmt::Import(expr, name, _) => { + #[cfg(feature = "no_module")] + unreachable!(); + + #[cfg(not(feature = "no_module"))] + { + if let Some(path) = self + .eval_expr(scope, state, fn_lib, expr, level)? + .try_cast::() + { + if let Some(resolver) = self.module_resolver.as_ref() { + let module = resolver.resolve(self, &path, expr.position())?; + + // TODO - avoid copying module name in inner block? + let mod_name = name.as_ref().clone(); + scope.push_module(mod_name, module); + Ok(Default::default()) + } else { + Err(Box::new(EvalAltResult::ErrorModuleNotFound( + path, + expr.position(), + ))) + } + } else { + Err(Box::new(EvalAltResult::ErrorImportExpr(expr.position()))) + } + } + } } } diff --git a/src/fn_call.rs b/src/fn_call.rs index a28522de..c64ba035 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -22,7 +22,7 @@ macro_rules! impl_args { fn into_vec(self) -> Vec { let ($($p,)*) = self; - #[allow(unused_variables, unused_mut)] + #[allow(unused_mut)] let mut v = Vec::new(); $(v.push($p.into_dynamic());)* @@ -42,5 +42,4 @@ macro_rules! impl_args { }; } -#[rustfmt::skip] impl_args!(A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V); diff --git a/src/fn_func.rs b/src/fn_func.rs index b9add7e4..af538306 100644 --- a/src/fn_func.rs +++ b/src/fn_func.rs @@ -116,5 +116,4 @@ macro_rules! def_anonymous_fn { }; } -#[rustfmt::skip] def_anonymous_fn!(A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V); diff --git a/src/fn_register.rs b/src/fn_register.rs index 90289784..3ebda970 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -3,11 +3,12 @@ #![allow(non_snake_case)] use crate::any::{Dynamic, Variant}; -use crate::engine::{calc_fn_spec, Engine, FnCallArgs}; +use crate::engine::{Engine, FnCallArgs}; use crate::result::EvalAltResult; use crate::token::Position; +use crate::utils::calc_fn_spec; -use crate::stdlib::{any::TypeId, boxed::Box, string::ToString}; +use crate::stdlib::{any::TypeId, boxed::Box, mem, string::ToString}; /// A trait to register custom plugins with the `Engine`. /// @@ -167,16 +168,19 @@ pub trait Plugin { pub struct Mut(T); //pub struct Ref(T); -/// Identity dereferencing function. +/// Dereference into &mut. #[inline] -pub fn identity(data: &mut T) -> &mut T { - data +pub fn by_ref(data: &mut Dynamic) -> &mut T { + // Directly cast the &mut Dynamic into &mut T to access the underlying data. + data.downcast_mut::().unwrap() } -/// Clone dereferencing function. +/// Dereference into value. #[inline] -pub fn cloned(data: &mut T) -> T { - data.clone() +pub fn by_value(data: &mut Dynamic) -> T { + // We consume the argument and then replace it with () - the argument is not supposed to be used again. + // This way, we avoid having to clone the argument again, because it is already a clone when passed here. + mem::take(data).cast::() } #[cfg(feature = "plugins")] @@ -194,7 +198,7 @@ macro_rules! count_args { /// This macro creates a closure wrapping a registered function. macro_rules! make_func { - ($fn_name:ident : $fn:ident : $map:expr ; $($par:ident => $clone:expr),*) => { + ($fn_name:ident : $fn:ident : $map:expr ; $($par:ident => $convert:expr),*) => { // ^ function name // ^ function pointer // ^ result mapping function @@ -212,14 +216,16 @@ macro_rules! make_func { #[allow(unused_variables, unused_mut)] let mut drain = args.iter_mut(); $( - // Downcast every element, return in case of a type mismatch - let $par: &mut $par = drain.next().unwrap().downcast_mut().unwrap(); + // Downcast every element, panic in case of a type mismatch (which shouldn't happen). + // Call the user-supplied function using ($convert) to access it either by value or by reference. + let $par = ($convert)(drain.next().unwrap()); )* - // Call the user-supplied function using ($clone) to - // potentially clone the value, otherwise pass the reference. - let r = $fn($(($clone)($par)),*); - $map(r, pos) + // Call the function with each parameter value + let r = $fn($($par),*); + + // Map the result + $map(r, pos) }; }; } @@ -318,18 +324,17 @@ macro_rules! def_register { //def_register!(imp_pop $($par => $mark => $param),*); }; ($p0:ident $(, $p:ident)*) => { - def_register!(imp $p0 => $p0 => $p0 => cloned $(, $p => $p => $p => cloned)*); - def_register!(imp $p0 => Mut<$p0> => &mut $p0 => identity $(, $p => $p => $p => cloned)*); + def_register!(imp $p0 => $p0 => $p0 => by_value $(, $p => $p => $p => by_value)*); + def_register!(imp $p0 => Mut<$p0> => &mut $p0 => by_ref $(, $p => $p => $p => by_value)*); // handle the first parameter ^ first parameter passed through - // ^ others passed by value (cloned) + // ^ others passed by value (by_value) // Currently does not support first argument which is a reference, as there will be // conflicting implementations since &T: Any and T: Any cannot be distinguished - //def_register!(imp $p0 => Ref<$p0> => &$p0 => identity $(, $p => $p => $p => Clone::clone)*); + //def_register!(imp $p0 => Ref<$p0> => &$p0 => by_ref $(, $p => $p => $p => by_value)*); def_register!($($p),*); }; } -#[rustfmt::skip] def_register!(A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V); diff --git a/src/lib.rs b/src/lib.rs index d442ae9a..84c45628 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,6 +76,7 @@ mod error; mod fn_call; mod fn_func; mod fn_register; +mod module; mod optimize; pub mod packages; mod parser; @@ -83,9 +84,10 @@ mod result; mod scope; mod stdlib; mod token; +mod utils; pub use any::Dynamic; -pub use engine::{calc_fn_spec as calc_fn_hash, Engine}; +pub use engine::Engine; pub use error::{ParseError, ParseErrorType}; pub use fn_call::FuncArgs; pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn}; @@ -95,6 +97,7 @@ pub use parser::{AST, INT}; pub use result::EvalAltResult; pub use scope::Scope; pub use token::Position; +pub use utils::calc_fn_spec as calc_fn_hash; #[cfg(not(feature = "no_function"))] pub use fn_func::Func; @@ -108,5 +111,13 @@ pub use engine::Map; #[cfg(not(feature = "no_float"))] pub use parser::FLOAT; +#[cfg(not(feature = "no_module"))] +pub use module::{Module, ModuleResolver}; + +#[cfg(not(feature = "no_module"))] +pub mod module_resolvers { + pub use crate::module::resolvers::*; +} + #[cfg(not(feature = "no_optimize"))] pub use optimize::OptimizationLevel; diff --git a/src/module.rs b/src/module.rs new file mode 100644 index 00000000..bdb545b1 --- /dev/null +++ b/src/module.rs @@ -0,0 +1,840 @@ +//! Module defining external-loaded modules for Rhai. +#![cfg(not(feature = "no_module"))] + +use crate::any::{Dynamic, Variant}; +use crate::calc_fn_hash; +use crate::engine::{Engine, FnAny, FnCallArgs, FunctionsLib}; +use crate::parser::{FnDef, AST}; +use crate::result::EvalAltResult; +use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; +use crate::token::Position; +use crate::token::Token; +use crate::utils::StaticVec; + +use crate::stdlib::{ + any::TypeId, + boxed::Box, + collections::HashMap, + fmt, mem, + ops::{Deref, DerefMut}, + rc::Rc, + string::{String, ToString}, + sync::Arc, +}; + +/// A trait that encapsulates a module resolution service. +pub trait ModuleResolver { + /// Resolve a module based on a path string. + fn resolve( + &self, + engine: &Engine, + path: &str, + pos: Position, + ) -> Result>; +} + +/// Return type of module-level Rust function. +type FuncReturn = Result>; + +/// An imported module, which may contain variables, sub-modules, +/// external Rust functions, and script-defined functions. +/// +/// Not available under the `no_module` feature. +#[derive(Default, Clone)] +pub struct Module { + /// Sub-modules. + modules: HashMap, + /// Module variables, including sub-modules. + variables: HashMap, + + /// External Rust functions. + #[cfg(not(feature = "sync"))] + functions: HashMap>>, + /// External Rust functions. + #[cfg(feature = "sync")] + functions: HashMap>>, + + /// Script-defined functions. + fn_lib: FunctionsLib, +} + +impl fmt::Debug for Module { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "", + self.variables, + self.functions.len(), + self.fn_lib.len() + ) + } +} + +impl Module { + /// Create a new module. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// module.set_var("answer", 42_i64); + /// assert_eq!(module.get_var_value::("answer").unwrap(), 42); + /// ``` + pub fn new() -> Self { + Default::default() + } + + /// Does a variable exist in the module? + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// module.set_var("answer", 42_i64); + /// assert!(module.contains_var("answer")); + /// ``` + pub fn contains_var(&self, name: &str) -> bool { + self.variables.contains_key(name) + } + + /// Get the value of a module variable. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// module.set_var("answer", 42_i64); + /// assert_eq!(module.get_var_value::("answer").unwrap(), 42); + /// ``` + pub fn get_var_value(&self, name: &str) -> Option { + self.get_var(name).and_then(|v| v.try_cast::()) + } + + /// Get a module variable as a `Dynamic`. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// module.set_var("answer", 42_i64); + /// assert_eq!(module.get_var("answer").unwrap().cast::(), 42); + /// ``` + pub fn get_var(&self, name: &str) -> Option { + self.variables.get(name).cloned() + } + + /// Get a mutable reference to a module variable. + pub fn get_var_mut(&mut self, name: &str) -> Option<&mut Dynamic> { + self.variables.get_mut(name) + } + + /// Set a variable into the module. + /// + /// If there is an existing variable of the same name, it is replaced. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// module.set_var("answer", 42_i64); + /// assert_eq!(module.get_var_value::("answer").unwrap(), 42); + /// ``` + pub fn set_var, T: Into>(&mut self, name: K, value: T) { + self.variables.insert(name.into(), value.into()); + } + + /// Get a mutable reference to a modules-qualified variable. + pub(crate) fn get_qualified_var_mut( + &mut self, + name: &str, + modules: &StaticVec<(String, Position)>, + pos: Position, + ) -> Result<&mut Dynamic, Box> { + Ok(self + .get_qualified_module_mut(modules)? + .get_var_mut(name) + .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))?) + } + + /// Does a sub-module exist in the module? + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let sub_module = Module::new(); + /// module.set_sub_module("question", sub_module); + /// assert!(module.contains_sub_module("question")); + /// ``` + pub fn contains_sub_module(&self, name: &str) -> bool { + self.modules.contains_key(name) + } + + /// Get a sub-module. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let sub_module = Module::new(); + /// module.set_sub_module("question", sub_module); + /// assert!(module.get_sub_module("question").is_some()); + /// ``` + pub fn get_sub_module(&self, name: &str) -> Option<&Module> { + self.modules.get(name) + } + + /// Get a mutable reference to a sub-module. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let sub_module = Module::new(); + /// module.set_sub_module("question", sub_module); + /// assert!(module.get_sub_module_mut("question").is_some()); + /// ``` + pub fn get_sub_module_mut(&mut self, name: &str) -> Option<&mut Module> { + self.modules.get_mut(name) + } + + /// Set a sub-module into the module. + /// + /// If there is an existing sub-module of the same name, it is replaced. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let sub_module = Module::new(); + /// module.set_sub_module("question", sub_module); + /// assert!(module.get_sub_module("question").is_some()); + /// ``` + pub fn set_sub_module>(&mut self, name: K, sub_module: Module) { + self.modules.insert(name.into(), sub_module.into()); + } + + /// Get a mutable reference to a modules chain. + /// The first module is always skipped and assumed to be the same as `self`. + pub(crate) fn get_qualified_module_mut( + &mut self, + modules: &StaticVec<(String, Position)>, + ) -> Result<&mut Module, Box> { + let mut drain = modules.iter(); + drain.next().unwrap(); // Skip first module + + let mut module = self; + + for (id, id_pos) in drain { + module = module + .get_sub_module_mut(id) + .ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *id_pos)))?; + } + + Ok(module) + } + + /// Does the particular Rust function exist in the module? + /// + /// The `u64` hash is calculated by the function `crate::calc_fn_hash`. + /// It is also returned by the `set_fn_XXX` calls. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let hash = module.set_fn_0("calc", || Ok(42_i64)); + /// assert!(module.contains_fn(hash)); + /// ``` + pub fn contains_fn(&self, hash: u64) -> bool { + self.functions.contains_key(&hash) + } + + /// Set a Rust function into the module, returning a hash key. + /// + /// If there is an existing Rust function of the same hash, it is replaced. + pub fn set_fn(&mut self, fn_name: &str, params: &[TypeId], func: Box) -> u64 { + let hash = calc_fn_hash(fn_name, params.iter().cloned()); + + #[cfg(not(feature = "sync"))] + self.functions.insert(hash, Rc::new(func)); + #[cfg(feature = "sync")] + self.functions.insert(hash, Arc::new(func)); + + hash + } + + /// Set a Rust function taking no parameters into the module, returning a hash key. + /// + /// If there is a similar existing Rust function, it is replaced. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let hash = module.set_fn_0("calc", || Ok(42_i64)); + /// assert!(module.get_fn(hash).is_some()); + /// ``` + pub fn set_fn_0>( + &mut self, + fn_name: &str, + #[cfg(not(feature = "sync"))] func: impl Fn() -> FuncReturn + 'static, + #[cfg(feature = "sync")] func: impl Fn() -> FuncReturn + Send + Sync + 'static, + ) -> u64 { + let f = move |_: &mut FnCallArgs, pos| { + func() + .map(|v| v.into()) + .map_err(|err| EvalAltResult::set_position(err, pos)) + }; + let arg_types = &[]; + self.set_fn(fn_name, arg_types, Box::new(f)) + } + + /// Set a Rust function taking one parameter into the module, returning a hash key. + /// + /// If there is a similar existing Rust function, it is replaced. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1)); + /// assert!(module.get_fn(hash).is_some()); + /// ``` + pub fn set_fn_1>( + &mut self, + fn_name: &str, + #[cfg(not(feature = "sync"))] func: impl Fn(A) -> FuncReturn + 'static, + #[cfg(feature = "sync")] func: impl Fn(A) -> FuncReturn + Send + Sync + 'static, + ) -> u64 { + let f = move |args: &mut FnCallArgs, pos| { + func(mem::take(args[0]).cast::()) + .map(|v| v.into()) + .map_err(|err| EvalAltResult::set_position(err, pos)) + }; + let arg_types = &[TypeId::of::()]; + self.set_fn(fn_name, arg_types, Box::new(f)) + } + + /// Set a Rust function taking one mutable parameter into the module, returning a hash key. + /// + /// If there is a similar existing Rust function, it is replaced. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let hash = module.set_fn_1_mut("calc", |x: &mut i64| { *x += 1; Ok(*x) }); + /// assert!(module.get_fn(hash).is_some()); + /// ``` + pub fn set_fn_1_mut>( + &mut self, + fn_name: &str, + #[cfg(not(feature = "sync"))] func: impl Fn(&mut A) -> FuncReturn + 'static, + #[cfg(feature = "sync")] func: impl Fn(&mut A) -> FuncReturn + Send + Sync + 'static, + ) -> u64 { + let f = move |args: &mut FnCallArgs, pos| { + func(args[0].downcast_mut::().unwrap()) + .map(|v| v.into()) + .map_err(|err| EvalAltResult::set_position(err, pos)) + }; + let arg_types = &[TypeId::of::()]; + self.set_fn(fn_name, arg_types, Box::new(f)) + } + + /// Set a Rust function taking two parameters into the module, returning a hash key. + /// + /// If there is a similar existing Rust function, it is replaced. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let hash = module.set_fn_2("calc", |x: i64, y: String| { + /// Ok(x + y.len() as i64) + /// }); + /// assert!(module.get_fn(hash).is_some()); + /// ``` + pub fn set_fn_2>( + &mut self, + fn_name: &str, + #[cfg(not(feature = "sync"))] func: impl Fn(A, B) -> FuncReturn + 'static, + #[cfg(feature = "sync")] func: impl Fn(A, B) -> FuncReturn + Send + Sync + 'static, + ) -> u64 { + let f = move |args: &mut FnCallArgs, pos| { + let a = mem::take(args[0]).cast::(); + let b = mem::take(args[1]).cast::(); + + func(a, b) + .map(|v| v.into()) + .map_err(|err| EvalAltResult::set_position(err, pos)) + }; + let arg_types = &[TypeId::of::(), TypeId::of::()]; + self.set_fn(fn_name, arg_types, Box::new(f)) + } + + /// Set a Rust function taking two parameters (the first one mutable) into the module, + /// returning a hash key. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let hash = module.set_fn_2_mut("calc", |x: &mut i64, y: String| { + /// *x += y.len() as i64; Ok(*x) + /// }); + /// assert!(module.get_fn(hash).is_some()); + /// ``` + pub fn set_fn_2_mut>( + &mut self, + fn_name: &str, + #[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> FuncReturn + 'static, + #[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> FuncReturn + Send + Sync + 'static, + ) -> u64 { + let f = move |args: &mut FnCallArgs, pos| { + let b = mem::take(args[1]).cast::(); + let a = args[0].downcast_mut::().unwrap(); + + func(a, b) + .map(|v| v.into()) + .map_err(|err| EvalAltResult::set_position(err, pos)) + }; + let arg_types = &[TypeId::of::(), TypeId::of::()]; + self.set_fn(fn_name, arg_types, Box::new(f)) + } + + /// Set a Rust function taking three parameters into the module, returning a hash key. + /// + /// If there is a similar existing Rust function, it is replaced. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let hash = module.set_fn_3("calc", |x: i64, y: String, z: i64| { + /// Ok(x + y.len() as i64 + z) + /// }); + /// assert!(module.get_fn(hash).is_some()); + /// ``` + pub fn set_fn_3< + A: Variant + Clone, + B: Variant + Clone, + C: Variant + Clone, + T: Into, + >( + &mut self, + fn_name: &str, + #[cfg(not(feature = "sync"))] func: impl Fn(A, B, C) -> FuncReturn + 'static, + #[cfg(feature = "sync")] func: impl Fn(A, B, C) -> FuncReturn + Send + Sync + 'static, + ) -> u64 { + let f = move |args: &mut FnCallArgs, pos| { + let a = mem::take(args[0]).cast::(); + let b = mem::take(args[1]).cast::(); + let c = mem::take(args[2]).cast::(); + + func(a, b, c) + .map(|v| v.into()) + .map_err(|err| EvalAltResult::set_position(err, pos)) + }; + let arg_types = &[TypeId::of::(), TypeId::of::(), TypeId::of::()]; + self.set_fn(fn_name, arg_types, Box::new(f)) + } + + /// Set a Rust function taking three parameters (the first one mutable) into the module, + /// returning a hash key. + /// + /// If there is a similar existing Rust function, it is replaced. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let hash = module.set_fn_3_mut("calc", |x: &mut i64, y: String, z: i64| { + /// *x += y.len() as i64 + z; Ok(*x) + /// }); + /// assert!(module.get_fn(hash).is_some()); + /// ``` + pub fn set_fn_3_mut< + A: Variant + Clone, + B: Variant + Clone, + C: Variant + Clone, + T: Into, + >( + &mut self, + fn_name: &str, + #[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B, C) -> FuncReturn + 'static, + #[cfg(feature = "sync")] func: impl Fn(&mut A, B, C) -> FuncReturn + Send + Sync + 'static, + ) -> u64 { + let f = move |args: &mut FnCallArgs, pos| { + let b = mem::take(args[1]).cast::(); + let c = mem::take(args[2]).cast::(); + let a = args[0].downcast_mut::().unwrap(); + + func(a, b, c) + .map(|v| v.into()) + .map_err(|err| EvalAltResult::set_position(err, pos)) + }; + let arg_types = &[TypeId::of::(), TypeId::of::(), TypeId::of::()]; + self.set_fn(fn_name, arg_types, Box::new(f)) + } + + /// Get a Rust function. + /// + /// The `u64` hash is calculated by the function `crate::calc_fn_hash`. + /// It is also returned by the `set_fn_XXX` calls. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1)); + /// assert!(module.get_fn(hash).is_some()); + /// ``` + pub fn get_fn(&self, hash: u64) -> Option<&Box> { + self.functions.get(&hash).map(|v| v.as_ref()) + } + + /// Get a modules-qualified function. + /// + /// The `u64` hash is calculated by the function `crate::calc_fn_hash`. + /// It is also returned by the `set_fn_XXX` calls. + pub(crate) fn get_qualified_fn( + &mut self, + name: &str, + hash: u64, + modules: &StaticVec<(String, Position)>, + pos: Position, + ) -> Result<&Box, Box> { + Ok(self + .get_qualified_module_mut(modules)? + .get_fn(hash) + .ok_or_else(|| { + let mut fn_name: String = Default::default(); + + modules.iter().for_each(|(n, _)| { + fn_name.push_str(n); + fn_name.push_str(Token::DoubleColon.syntax().as_ref()); + }); + + fn_name.push_str(name); + + Box::new(EvalAltResult::ErrorFunctionNotFound(fn_name, pos)) + })?) + } + + /// Get the script-defined functions. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// assert_eq!(module.get_fn_lib().len(), 0); + /// ``` + pub fn get_fn_lib(&self) -> &FunctionsLib { + &self.fn_lib + } + + /// Get a modules-qualified functions library. + pub(crate) fn get_qualified_fn_lib( + &mut self, + name: &str, + args: usize, + modules: &StaticVec<(String, Position)>, + ) -> Result, Box> { + Ok(self + .get_qualified_module_mut(modules)? + .fn_lib + .get_function(name, args)) + } + + /// Create a new `Module` by evaluating an `AST`. + /// + /// # Examples + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::{Engine, Module}; + /// + /// let engine = Engine::new(); + /// let ast = engine.compile("let answer = 42;")?; + /// let module = Module::eval_ast_as_new(&ast, &engine)?; + /// assert!(module.contains_var("answer")); + /// assert_eq!(module.get_var_value::("answer").unwrap(), 42); + /// # Ok(()) + /// # } + /// ``` + pub fn eval_ast_as_new(ast: &AST, engine: &Engine) -> FuncReturn { + // Use new scope + let mut scope = Scope::new(); + + // Run the script + engine.eval_ast_with_scope_raw(&mut scope, &ast)?; + + // Create new module + let mut module = Module::new(); + + scope.into_iter().for_each( + |ScopeEntry { + name, typ, value, .. + }| { + match typ { + // Variables left in the scope become module variables + ScopeEntryType::Normal | ScopeEntryType::Constant => { + module.variables.insert(name.into_owned(), value); + } + // Modules left in the scope become sub-modules + ScopeEntryType::Module => { + module + .modules + .insert(name.into_owned(), value.cast::()); + } + } + }, + ); + + module.fn_lib = module.fn_lib.merge(ast.fn_lib()); + + Ok(module) + } +} + +/// Re-export module resolvers. +pub mod resolvers { + #[cfg(not(feature = "no_std"))] + pub use super::file::FileModuleResolver; + pub use super::stat::StaticModuleResolver; +} + +/// Script file-based module resolver. +#[cfg(not(feature = "no_std"))] +mod file { + use super::*; + use crate::stdlib::path::PathBuf; + + /// A module resolution service that loads module script files from the file system. + /// + /// The `new_with_path` and `new_with_path_and_extension` constructor functions + /// allow specification of a base directory with module path used as a relative path offset + /// to the base directory. The script file is then forced to be in a specified extension + /// (default `.rhai`). + /// + /// # Examples + /// + /// ``` + /// use rhai::Engine; + /// use rhai::module_resolvers::FileModuleResolver; + /// + /// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory + /// // with file extension '.x'. + /// let resolver = FileModuleResolver::new_with_path_and_extension("./scripts", "x"); + /// + /// let mut engine = Engine::new(); + /// engine.set_module_resolver(Some(resolver)); + /// ``` + #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)] + pub struct FileModuleResolver { + path: PathBuf, + extension: String, + } + + impl FileModuleResolver { + /// Create a new `FileModuleResolver` with a specific base path. + /// + /// # Examples + /// + /// ``` + /// use rhai::Engine; + /// use rhai::module_resolvers::FileModuleResolver; + /// + /// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory + /// // with file extension '.rhai' (the default). + /// let resolver = FileModuleResolver::new_with_path("./scripts"); + /// + /// let mut engine = Engine::new(); + /// engine.set_module_resolver(Some(resolver)); + /// ``` + pub fn new_with_path>(path: P) -> Self { + Self::new_with_path_and_extension(path, "rhai") + } + + /// Create a new `FileModuleResolver` with a specific base path and file extension. + /// + /// The default extension is `.rhai`. + /// + /// # Examples + /// + /// ``` + /// use rhai::Engine; + /// use rhai::module_resolvers::FileModuleResolver; + /// + /// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory + /// // with file extension '.x'. + /// let resolver = FileModuleResolver::new_with_path_and_extension("./scripts", "x"); + /// + /// let mut engine = Engine::new(); + /// engine.set_module_resolver(Some(resolver)); + /// ``` + pub fn new_with_path_and_extension, E: Into>( + path: P, + extension: E, + ) -> Self { + Self { + path: path.into(), + extension: extension.into(), + } + } + + /// Create a new `FileModuleResolver` with the current directory as base path. + /// + /// # Examples + /// + /// ``` + /// use rhai::Engine; + /// use rhai::module_resolvers::FileModuleResolver; + /// + /// // Create a new 'FileModuleResolver' loading scripts from the current directory + /// // with file extension '.rhai' (the default). + /// let resolver = FileModuleResolver::new(); + /// + /// let mut engine = Engine::new(); + /// engine.set_module_resolver(Some(resolver)); + /// ``` + pub fn new() -> Self { + Default::default() + } + } + + impl ModuleResolver for FileModuleResolver { + fn resolve( + &self, + engine: &Engine, + path: &str, + pos: Position, + ) -> Result> { + // Construct the script file path + let mut file_path = self.path.clone(); + file_path.push(path); + file_path.set_extension(&self.extension); // Force extension + + // Compile it + let ast = engine + .compile_file(file_path) + .map_err(|err| EvalAltResult::set_position(err, pos))?; + + Module::eval_ast_as_new(&ast, engine) + .map_err(|err| EvalAltResult::set_position(err, pos)) + } + } +} + +/// Static module resolver. +mod stat { + use super::*; + + /// A module resolution service that serves modules added into it. + /// + /// # Examples + /// + /// ``` + /// use rhai::{Engine, Module}; + /// use rhai::module_resolvers::StaticModuleResolver; + /// + /// let mut resolver = StaticModuleResolver::new(); + /// + /// let module = Module::new(); + /// resolver.insert("hello".to_string(), module); + /// + /// let mut engine = Engine::new(); + /// engine.set_module_resolver(Some(resolver)); + /// ``` + #[derive(Debug, Clone, Default)] + pub struct StaticModuleResolver(HashMap); + + impl StaticModuleResolver { + /// Create a new `StaticModuleResolver`. + /// + /// # Examples + /// + /// ``` + /// use rhai::{Engine, Module}; + /// use rhai::module_resolvers::StaticModuleResolver; + /// + /// let mut resolver = StaticModuleResolver::new(); + /// + /// let module = Module::new(); + /// resolver.insert("hello".to_string(), module); + /// + /// let mut engine = Engine::new(); + /// engine.set_module_resolver(Some(resolver)); + /// ``` + pub fn new() -> Self { + Default::default() + } + } + + impl Deref for StaticModuleResolver { + type Target = HashMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl DerefMut for StaticModuleResolver { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + + impl ModuleResolver for StaticModuleResolver { + fn resolve( + &self, + _: &Engine, + path: &str, + pos: Position, + ) -> Result> { + self.0 + .get(path) + .cloned() + .ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(path.to_string(), pos))) + } + } +} diff --git a/src/optimize.rs b/src/optimize.rs index 268c504a..33765a56 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -13,9 +13,7 @@ use crate::token::Position; use crate::stdlib::{ boxed::Box, collections::HashMap, - rc::Rc, string::{String, ToString}, - sync::Arc, vec, vec::Vec, }; @@ -231,6 +229,8 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) - } // let id; Stmt::Let(_, None, _) => stmt, + // import expr as id; + Stmt::Import(expr, id, pos) => Stmt::Import(Box::new(optimize_expr(*expr, state)), id, pos), // { block } Stmt::Block(block, pos) => { let orig_len = block.len(); // Original number of statements in the block, for change detection @@ -260,7 +260,7 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) - result.push(stmt); } - // Remove all let statements at the end of a block - the new variables will go away anyway. + // Remove all let/import statements at the end of a block - the new variables will go away anyway. // But be careful only remove ones that have no initial values or have values that are pure expressions, // otherwise there may be side effects. let mut removed = false; @@ -268,7 +268,8 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) - while let Some(expr) = result.pop() { match expr { Stmt::Let(_, None, _) => removed = true, - Stmt::Let(_, Some(val_expr), _) if val_expr.is_pure() => removed = true, + Stmt::Let(_, Some(val_expr), _) => removed = val_expr.is_pure(), + Stmt::Import(expr, _, _) => removed = expr.is_pure(), _ => { result.push(expr); break; @@ -323,6 +324,8 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) - state.set_dirty(); Stmt::Noop(pos) } + // Only one let/import statement - leave it alone + [Stmt::Let(_, _, _)] | [Stmt::Import(_, _, _)] => Stmt::Block(result, pos), // Only one statement - promote [_] => { state.set_dirty(); @@ -368,15 +371,12 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { //id = id2 = expr2 Expr::Assignment(id2, expr2, pos2) => match (*id, *id2) { // var = var = expr2 -> var = expr2 - (Expr::Variable(var, sp, _), Expr::Variable(var2, sp2, _)) if var == var2 && sp == sp2 => { + (Expr::Variable(var, None, sp, _), Expr::Variable(var2, None, sp2, _)) + if var == var2 && sp == sp2 => + { // Assignment to the same variable - fold state.set_dirty(); - - Expr::Assignment( - Box::new(Expr::Variable(var, sp, pos)), - Box::new(optimize_expr(*expr2, state)), - pos, - ) + Expr::Assignment(Box::new(Expr::Variable(var, None, sp, pos)), Box::new(optimize_expr(*expr2, state)), pos) } // id1 = id2 = expr2 (id1, id2) => Expr::Assignment( @@ -552,18 +552,18 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { }, // Do not call some special keywords - Expr::FnCall(id, args, def_value, pos) if DONT_EVAL_KEYWORDS.contains(&id.as_ref().as_ref())=> - Expr::FnCall(id, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos), + Expr::FnCall(id, None, args, def_value, pos) if DONT_EVAL_KEYWORDS.contains(&id.as_ref().as_ref())=> + Expr::FnCall(id, None, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos), // Eagerly call functions - Expr::FnCall(id, args, def_value, pos) + Expr::FnCall(id, None, args, def_value, pos) if state.optimization_level == OptimizationLevel::Full // full optimizations && args.iter().all(|expr| expr.is_constant()) // all arguments are constants => { // First search in script-defined functions (can override built-in) if state.fn_lib.iter().find(|(name, len)| name == id.as_ref() && *len == args.len()).is_some() { // A script-defined function overrides the built-in function - do not make the call - return Expr::FnCall(id, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos); + return Expr::FnCall(id, None, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos); } let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect(); @@ -594,16 +594,16 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { }) ).unwrap_or_else(|| // Optimize function call arguments - Expr::FnCall(id, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos) + Expr::FnCall(id, None, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos) ) } // id(args ..) -> optimize function call arguments - Expr::FnCall(id, args, def_value, pos) => - Expr::FnCall(id, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos), + Expr::FnCall(id, modules, args, def_value, pos) => + Expr::FnCall(id, modules, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos), // constant-name - Expr::Variable(name, _, pos) if state.contains_constant(&name) => { + Expr::Variable(name, None, _, pos) if state.contains_constant(&name) => { state.set_dirty(); // Replace constant with value @@ -669,7 +669,10 @@ fn optimize<'a>( _ => { // Keep all variable declarations at this level // and always keep the last return value - let keep = matches!(stmt, Stmt::Let(_, _, _)) || i == num_statements - 1; + let keep = match stmt { + Stmt::Let(_, _, _) | Stmt::Import(_, _, _) => true, + _ => i == num_statements - 1, + }; optimize_stmt(stmt, &mut state, keep) } } @@ -708,11 +711,16 @@ pub fn optimize_into_ast( #[cfg(feature = "no_optimize")] const level: OptimizationLevel = OptimizationLevel::None; + #[cfg(not(feature = "no_function"))] let fn_lib: Vec<_> = functions .iter() .map(|fn_def| (fn_def.name.as_str(), fn_def.params.len())) .collect(); + #[cfg(feature = "no_function")] + const fn_lib: &[(&str, usize)] = &[]; + + #[cfg(not(feature = "no_function"))] let lib = FunctionsLib::from_vec( functions .iter() @@ -742,16 +750,16 @@ pub fn optimize_into_ast( .collect(), ); - AST( + #[cfg(feature = "no_function")] + let lib: FunctionsLib = Default::default(); + + AST::new( match level { OptimizationLevel::None => statements, OptimizationLevel::Simple | OptimizationLevel::Full => { optimize(statements, engine, &scope, &fn_lib, level) } }, - #[cfg(feature = "sync")] - Arc::new(lib), - #[cfg(not(feature = "sync"))] - Rc::new(lib), + lib, ) } diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index ca69f3fc..dfb80422 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -1,3 +1,5 @@ +#![cfg(not(feature = "no_index"))] + use super::{reg_binary, reg_binary_mut, reg_trinary_mut, reg_unary_mut}; use crate::any::{Dynamic, Variant}; diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 40e2cec0..451897bf 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -1,3 +1,5 @@ +#![cfg(not(feature = "no_object"))] + use super::{reg_binary, reg_binary_mut, reg_unary_mut}; use crate::any::Dynamic; diff --git a/src/packages/mod.rs b/src/packages/mod.rs index 54c3f177..3734bd25 100644 --- a/src/packages/mod.rs +++ b/src/packages/mod.rs @@ -47,6 +47,7 @@ pub trait Package { } /// Type to store all functions in the package. +#[derive(Default)] pub struct PackageStore { /// All functions, keyed by a hash created from the function name and parameter types. pub functions: HashMap>, @@ -58,10 +59,7 @@ pub struct PackageStore { impl PackageStore { /// Create a new `PackageStore`. pub fn new() -> Self { - Self { - functions: HashMap::new(), - type_iterators: HashMap::new(), - } + Default::default() } } diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 87807d70..e58890d1 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -1,10 +1,16 @@ use super::{reg_binary, reg_binary_mut, reg_none, reg_unary, reg_unary_mut}; use crate::def_package; -use crate::engine::{Array, Map, FUNC_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT}; +use crate::engine::{FUNC_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT}; use crate::fn_register::map_dynamic as map; use crate::parser::INT; +#[cfg(not(feature = "no_index"))] +use crate::engine::Array; + +#[cfg(not(feature = "no_object"))] +use crate::engine::Map; + use crate::stdlib::{ fmt::{Debug, Display}, format, @@ -18,6 +24,7 @@ fn to_debug(x: &mut T) -> String { fn to_string(x: &mut T) -> String { format!("{}", x) } +#[cfg(not(feature = "no_object"))] fn format_map(x: &mut Map) -> String { format!("#{:?}", x) } diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 0144792e..2badac23 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -1,10 +1,12 @@ use super::{reg_binary, reg_binary_mut, reg_trinary_mut, reg_unary_mut}; use crate::def_package; -use crate::engine::Array; use crate::fn_register::map_dynamic as map; use crate::parser::INT; +#[cfg(not(feature = "no_index"))] +use crate::engine::Array; + use crate::stdlib::{ fmt::Display, format, diff --git a/src/parser.rs b/src/parser.rs index e682ee12..550feeb0 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,11 +1,12 @@ //! Main module defining the lexer and parser. use crate::any::{Dynamic, Union}; -use crate::engine::{calc_fn_def, Engine, FunctionsLib}; +use crate::engine::{Engine, FunctionsLib}; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::token::{Position, Token, TokenIterator}; +use crate::utils::{calc_fn_def, StaticVec}; use crate::stdlib::{ borrow::Cow, @@ -15,7 +16,7 @@ use crate::stdlib::{ format, iter::Peekable, num::NonZeroUsize, - ops::Add, + ops::{Add, Deref, DerefMut}, rc::Rc, string::{String, ToString}, sync::Arc, @@ -38,24 +39,53 @@ pub type INT = i32; /// The system floating-point type. /// /// Not available under the `no_float` feature. +#[cfg(not(feature = "no_float"))] pub type FLOAT = f64; type PERR = ParseErrorType; +/// A chain of module names to qualify a variable or function call. +/// 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. +pub type ModuleRef = Option>>; + /// Compiled AST (abstract syntax tree) of a Rhai script. /// /// Currently, `AST` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct AST( - pub(crate) Vec, - #[cfg(feature = "sync")] pub(crate) Arc, - #[cfg(not(feature = "sync"))] pub(crate) Rc, + /// Global statements. + Vec, + /// Script-defined functions, wrapped in an `Arc` for shared access. + #[cfg(feature = "sync")] + Arc, + /// Script-defined functions, wrapped in an `Rc` for shared access. + #[cfg(not(feature = "sync"))] + Rc, ); impl AST { /// Create a new `AST`. - pub fn new() -> Self { - Default::default() + pub fn new(statements: Vec, fn_lib: FunctionsLib) -> Self { + #[cfg(feature = "sync")] + return Self(statements, Arc::new(fn_lib)); + #[cfg(not(feature = "sync"))] + return Self(statements, Rc::new(fn_lib)); + } + + /// Get the statements. + pub(crate) fn statements(&self) -> &Vec { + &self.0 + } + + /// Get a mutable reference to the statements. + pub(crate) fn statements_mut(&mut self) -> &mut Vec { + &mut self.0 + } + + /// Get the script-defined functions. + pub(crate) fn fn_lib(&self) -> &FunctionsLib { + self.1.as_ref() } /// Merge two `AST` into one. Both `AST`'s are untouched and a new, merged, version @@ -114,17 +144,11 @@ impl AST { (true, true) => vec![], }; - #[cfg(feature = "sync")] - { - Self(ast, Arc::new(functions.merge(other.1.as_ref()))) - } - #[cfg(not(feature = "sync"))] - { - Self(ast, Rc::new(functions.merge(other.1.as_ref()))) - } + Self::new(ast, functions.merge(other.1.as_ref())) } /// Clear all function definitions in the `AST`. + #[cfg(not(feature = "no_function"))] pub fn clear_functions(&mut self) { #[cfg(feature = "sync")] { @@ -137,23 +161,12 @@ impl AST { } /// Clear all statements in the `AST`, leaving only function definitions. + #[cfg(not(feature = "no_function"))] pub fn retain_functions(&mut self) { self.0 = vec![]; } } -impl Default for AST { - fn default() -> Self { - #[cfg(feature = "sync")] - { - Self(vec![], Arc::new(FunctionsLib::new())) - } - #[cfg(not(feature = "sync"))] - { - Self(vec![], Rc::new(FunctionsLib::new())) - } - } -} impl Add for &AST { type Output = AST; @@ -185,25 +198,13 @@ pub enum ReturnType { } /// A type that encapsulates a local stack with variable names to simulate an actual runtime scope. -#[derive(Debug, Clone)] -struct Stack(Vec); +#[derive(Debug, Clone, Default)] +struct Stack(Vec<(String, ScopeEntryType)>); impl Stack { /// Create a new `Stack`. pub fn new() -> Self { - Self(Vec::new()) - } - /// Get the number of variables in the `Stack`. - pub fn len(&self) -> usize { - self.0.len() - } - /// Push (add) a new variable onto the `Stack`. - pub fn push(&mut self, name: String) { - self.0.push(name); - } - /// Rewind the stack to a previous size. - pub fn rewind(&mut self, len: usize) { - self.0.truncate(len); + Default::default() } /// Find a variable by name in the `Stack`, searching in reverse. /// The return value is the offset to be deducted from `Stack::len`, @@ -214,9 +215,41 @@ impl Stack { .iter() .rev() .enumerate() - .find(|(_, n)| *n == name) + .find(|(_, (n, typ))| match typ { + ScopeEntryType::Normal | ScopeEntryType::Constant => *n == name, + ScopeEntryType::Module => false, + }) .and_then(|(i, _)| NonZeroUsize::new(i + 1)) } + /// Find a sub-scope by name in the `Stack`, searching in reverse. + /// The return value is the offset to be deducted from `Stack::len`, + /// i.e. the top element of the `Stack` is offset 1. + /// Return zero when the variable name is not found in the `Stack`. + pub fn find_sub_scope(&self, name: &str) -> Option { + self.0 + .iter() + .rev() + .enumerate() + .find(|(_, (n, typ))| match typ { + ScopeEntryType::Module => *n == name, + ScopeEntryType::Normal | ScopeEntryType::Constant => false, + }) + .and_then(|(i, _)| NonZeroUsize::new(i + 1)) + } +} + +impl Deref for Stack { + type Target = Vec<(String, ScopeEntryType)>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Stack { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } } /// A statement. @@ -244,8 +277,10 @@ pub enum Stmt { Continue(Position), /// break Break(Position), - /// `return`/`throw` + /// return/throw ReturnWithVal(Option>, ReturnType, Position), + /// import expr as module + Import(Box, Box, Position), } impl Stmt { @@ -255,11 +290,14 @@ impl Stmt { Stmt::Noop(pos) | Stmt::Let(_, _, pos) | Stmt::Const(_, _, pos) + | Stmt::Import(_, _, pos) | Stmt::Block(_, pos) | Stmt::Continue(pos) | Stmt::Break(pos) | Stmt::ReturnWithVal(_, _, pos) => *pos, + Stmt::IfThenElse(expr, _, _) | Stmt::Expr(expr) => expr.position(), + Stmt::While(_, stmt) | Stmt::Loop(stmt) | Stmt::For(_, _, stmt) => stmt.position(), } } @@ -278,6 +316,7 @@ impl Stmt { Stmt::Let(_, _, _) | Stmt::Const(_, _, _) + | Stmt::Import(_, _, _) | Stmt::Expr(_) | Stmt::Continue(_) | Stmt::Break(_) @@ -301,6 +340,7 @@ impl Stmt { Stmt::Let(_, _, _) | Stmt::Const(_, _, _) => false, Stmt::Block(statements, _) => statements.iter().all(Stmt::is_pure), Stmt::Continue(_) | Stmt::Break(_) | Stmt::ReturnWithVal(_, _, _) => false, + Stmt::Import(_, _, _) => false, } } } @@ -311,22 +351,24 @@ pub enum Expr { /// Integer constant. IntegerConstant(INT, Position), /// Floating-point constant. + #[cfg(not(feature = "no_float"))] FloatConstant(FLOAT, Position), /// Character constant. CharConstant(char, Position), /// String constant. StringConstant(String, Position), - /// Variable access. - Variable(Box, Option, Position), + /// Variable access - (variable name, optional modules, optional index, position) + Variable(Box, ModuleRef, Option, Position), /// Property access. Property(String, Position), /// { stmt } Stmt(Box, Position), - /// func(expr, ... ) + /// func(expr, ... ) - (function name, optional modules, arguments, optional default value, position) /// Use `Cow<'static, str>` because a lot of operators (e.g. `==`, `>=`) are implemented as function calls /// and the function names are predictable, so no need to allocate a new `String`. FnCall( Box>, + ModuleRef, Box>, Option>, Position, @@ -372,6 +414,7 @@ impl Expr { Self::False(_) => false.into(), Self::Unit(_) => ().into(), + #[cfg(not(feature = "no_index"))] Self::Array(items, _) if items.iter().all(Self::is_constant) => { Dynamic(Union::Array(Box::new( items @@ -381,6 +424,7 @@ impl Expr { ))) } + #[cfg(not(feature = "no_object"))] Self::Map(items, _) if items.iter().all(|(_, v, _)| v.is_constant()) => { Dynamic(Union::Map(Box::new( items @@ -401,8 +445,10 @@ impl Expr { /// Panics when the expression is not constant. pub fn get_constant_str(&self) -> String { match self { - Self::IntegerConstant(i, _) => i.to_string(), + #[cfg(not(feature = "no_float"))] Self::FloatConstant(f, _) => f.to_string(), + + Self::IntegerConstant(i, _) => i.to_string(), Self::CharConstant(c, _) => c.to_string(), Self::StringConstant(_, _) => "string".to_string(), Self::True(_) => "true".to_string(), @@ -418,16 +464,18 @@ impl Expr { /// Get the `Position` of the expression. pub fn position(&self) -> Position { match self { + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(_, pos) => *pos, + Self::IntegerConstant(_, pos) - | Self::FloatConstant(_, pos) | Self::CharConstant(_, pos) | Self::StringConstant(_, pos) | Self::Array(_, pos) | Self::Map(_, pos) - | Self::Variable(_, _, pos) + | Self::Variable(_, _, _, pos) | Self::Property(_, pos) | Self::Stmt(_, pos) - | Self::FnCall(_, _, _, pos) + | Self::FnCall(_, _, _, _, pos) | Self::And(_, _, pos) | Self::Or(_, _, pos) | Self::In(_, _, pos) @@ -444,16 +492,18 @@ impl Expr { /// Get the `Position` of the expression. pub(crate) fn set_position(mut self, new_pos: Position) -> Self { match &mut self { + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(_, pos) => *pos = new_pos, + Self::IntegerConstant(_, pos) - | Self::FloatConstant(_, pos) | Self::CharConstant(_, pos) | Self::StringConstant(_, pos) | Self::Array(_, pos) | Self::Map(_, pos) - | Self::Variable(_, _, pos) + | Self::Variable(_, _, _, pos) | Self::Property(_, pos) | Self::Stmt(_, pos) - | Self::FnCall(_, _, _, pos) + | Self::FnCall(_, _, _, _, pos) | Self::And(_, _, pos) | Self::Or(_, _, pos) | Self::In(_, _, pos) @@ -481,7 +531,7 @@ impl Expr { Self::Stmt(stmt, _) => stmt.is_pure(), - Self::Variable(_, _, _) => true, + Self::Variable(_, _, _, _) => true, expr => expr.is_constant(), } @@ -490,8 +540,10 @@ impl Expr { /// Is the expression a constant? pub fn is_constant(&self) -> bool { match self { + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(_, _) => true, + Self::IntegerConstant(_, _) - | Self::FloatConstant(_, _) | Self::CharConstant(_, _) | Self::StringConstant(_, _) | Self::True(_) @@ -518,8 +570,10 @@ impl Expr { /// Is a particular token allowed as a postfix operator to this expression? pub fn is_valid_postfix(&self, token: &Token) -> bool { match self { + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(_, _) => false, + Self::IntegerConstant(_, _) - | Self::FloatConstant(_, _) | Self::CharConstant(_, _) | Self::In(_, _, _) | Self::And(_, _, _) @@ -530,7 +584,7 @@ impl Expr { Self::StringConstant(_, _) | Self::Stmt(_, _) - | Self::FnCall(_, _, _, _) + | Self::FnCall(_, _, _, _, _) | Self::Assignment(_, _, _) | Self::Dot(_, _, _) | Self::Index(_, _, _) @@ -540,7 +594,14 @@ impl Expr { _ => false, }, - Self::Variable(_, _, _) | Self::Property(_, _) => match token { + Self::Variable(_, _, _, _) => match token { + Token::LeftBracket | Token::LeftParen => true, + #[cfg(not(feature = "no_module"))] + Token::DoubleColon => true, + _ => false, + }, + + Self::Property(_, _) => match token { Token::LeftBracket | Token::LeftParen => true, _ => false, }, @@ -550,7 +611,7 @@ impl Expr { /// Convert a `Variable` into a `Property`. All other variants are untouched. pub(crate) fn into_property(self) -> Self { match self { - Self::Variable(id, _, pos) => Self::Property(*id, pos), + Self::Variable(id, None, _, pos) => Self::Property(*id, pos), _ => self, } } @@ -586,11 +647,11 @@ fn match_token(input: &mut Peekable, token: Token) -> Result( input: &mut Peekable>, stack: &mut Stack, - begin: Position, + pos: Position, allow_stmt_expr: bool, ) -> Result> { if match_token(input, Token::RightParen)? { - return Ok(Expr::Unit(begin)); + return Ok(Expr::Unit(pos)); } let expr = parse_expr(input, stack, allow_stmt_expr)?; @@ -602,7 +663,7 @@ fn parse_paren_expr<'a>( (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), // ( xxx ??? (_, pos) => Err(PERR::MissingToken( - ")".into(), + Token::RightParen.into(), "for a matching ( in this expression".into(), ) .into_err(pos)), @@ -614,6 +675,7 @@ fn parse_call_expr<'a>( input: &mut Peekable>, stack: &mut Stack, id: String, + modules: ModuleRef, begin: Position, allow_stmt_expr: bool, ) -> Result> { @@ -623,7 +685,7 @@ fn parse_call_expr<'a>( // id (Token::EOF, pos) => { return Err(PERR::MissingToken( - ")".into(), + Token::RightParen.into(), format!("to close the arguments list of this function call '{}'", id), ) .into_err(*pos)) @@ -635,6 +697,7 @@ fn parse_call_expr<'a>( eat_token(input, Token::RightParen); return Ok(Expr::FnCall( Box::new(id.into()), + modules, Box::new(args), None, begin, @@ -650,8 +713,10 @@ fn parse_call_expr<'a>( match input.peek().unwrap() { (Token::RightParen, _) => { eat_token(input, Token::RightParen); + return Ok(Expr::FnCall( Box::new(id.into()), + modules, Box::new(args), None, begin, @@ -662,7 +727,7 @@ fn parse_call_expr<'a>( } (Token::EOF, pos) => { return Err(PERR::MissingToken( - ")".into(), + Token::RightParen.into(), format!("to close the arguments list of this function call '{}'", id), ) .into_err(*pos)) @@ -672,7 +737,7 @@ fn parse_call_expr<'a>( } (_, pos) => { return Err(PERR::MissingToken( - ",".into(), + Token::Comma.into(), format!("to separate the arguments to function call '{}'", id), ) .into_err(*pos)) @@ -712,8 +777,15 @@ fn parse_index_chain<'a>( .into_err(*pos)) } - Expr::FloatConstant(_, pos) - | Expr::CharConstant(_, pos) + #[cfg(not(feature = "no_float"))] + Expr::FloatConstant(_, pos) => { + return Err(PERR::MalformedIndexExpr( + "Only arrays, object maps and strings can be indexed".into(), + ) + .into_err(pos)) + } + + Expr::CharConstant(_, pos) | Expr::Assignment(_, _, pos) | Expr::And(_, _, pos) | Expr::Or(_, _, pos) @@ -740,8 +812,16 @@ fn parse_index_chain<'a>( ) .into_err(*pos)) } - Expr::FloatConstant(_, pos) - | Expr::CharConstant(_, pos) + + #[cfg(not(feature = "no_float"))] + Expr::FloatConstant(_, pos) => { + return Err(PERR::MalformedIndexExpr( + "Only arrays, object maps and strings can be indexed".into(), + ) + .into_err(pos)) + } + + Expr::CharConstant(_, pos) | Expr::Assignment(_, _, pos) | Expr::And(_, _, pos) | Expr::Or(_, _, pos) @@ -759,6 +839,7 @@ fn parse_index_chain<'a>( }, // lhs[float] + #[cfg(not(feature = "no_float"))] Expr::FloatConstant(_, pos) => { return Err(PERR::MalformedIndexExpr( "Array access expects integer index, not a float".into(), @@ -816,7 +897,7 @@ fn parse_index_chain<'a>( } (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(*pos)), (_, pos) => Err(PERR::MissingToken( - "]".into(), + Token::RightBracket.into(), "for a matching [ in this index expression".into(), ) .into_err(*pos)), @@ -827,7 +908,7 @@ fn parse_index_chain<'a>( fn parse_array_literal<'a>( input: &mut Peekable>, stack: &mut Stack, - begin: Position, + pos: Position, allow_stmt_expr: bool, ) -> Result> { let mut arr = Vec::new(); @@ -843,17 +924,18 @@ fn parse_array_literal<'a>( break; } (Token::EOF, pos) => { - return Err( - PERR::MissingToken("]".into(), "to end this array literal".into()) - .into_err(*pos), + return Err(PERR::MissingToken( + Token::RightBracket.into(), + "to end this array literal".into(), ) + .into_err(*pos)) } (Token::LexError(err), pos) => { return Err(PERR::BadInput(err.to_string()).into_err(*pos)) } (_, pos) => { return Err(PERR::MissingToken( - ",".into(), + Token::Comma.into(), "to separate the items of this array literal".into(), ) .into_err(*pos)) @@ -862,14 +944,14 @@ fn parse_array_literal<'a>( } } - Ok(Expr::Array(arr, begin)) + Ok(Expr::Array(arr, pos)) } /// Parse a map literal. fn parse_map_literal<'a>( input: &mut Peekable>, stack: &mut Stack, - begin: Position, + pos: Position, allow_stmt_expr: bool, ) -> Result> { let mut map = Vec::new(); @@ -885,10 +967,16 @@ fn parse_map_literal<'a>( return Err(PERR::BadInput(err.to_string()).into_err(pos)) } (_, pos) if map.is_empty() => { - return Err(PERR::MissingToken("}".into(), MISSING_RBRACE.into()).into_err(pos)) + return Err( + PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into()) + .into_err(pos), + ) } (Token::EOF, pos) => { - return Err(PERR::MissingToken("}".into(), MISSING_RBRACE.into()).into_err(pos)) + return Err( + PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into()) + .into_err(pos), + ) } (_, pos) => return Err(PERR::PropertyExpected.into_err(pos)), }; @@ -900,7 +988,7 @@ fn parse_map_literal<'a>( } (_, pos) => { return Err(PERR::MissingToken( - ":".into(), + Token::Colon.into(), format!( "to follow the property '{}' in this object map literal", name @@ -924,7 +1012,7 @@ fn parse_map_literal<'a>( } (Token::Identifier(_), pos) => { return Err(PERR::MissingToken( - ",".into(), + Token::Comma.into(), "to separate the items of this object map literal".into(), ) .into_err(*pos)) @@ -933,7 +1021,10 @@ fn parse_map_literal<'a>( return Err(PERR::BadInput(err.to_string()).into_err(*pos)) } (_, pos) => { - return Err(PERR::MissingToken("}".into(), MISSING_RBRACE.into()).into_err(*pos)) + return Err( + PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into()) + .into_err(*pos), + ) } } } @@ -950,7 +1041,7 @@ fn parse_map_literal<'a>( }) .map_err(|(key, pos)| PERR::DuplicatedProperty(key.to_string()).into_err(pos))?; - Ok(Expr::Map(map, begin)) + Ok(Expr::Map(map, pos)) } /// Parse a primary expression. @@ -978,7 +1069,7 @@ fn parse_primary<'a>( Token::StringConst(s) => Expr::StringConstant(s, pos), Token::Identifier(s) => { let index = stack.find(&s); - Expr::Variable(Box::new(s), index, pos) + Expr::Variable(Box::new(s), None, index, pos) } Token::LeftParen => parse_paren_expr(input, stack, pos, allow_stmt_expr)?, #[cfg(not(feature = "no_index"))] @@ -1001,19 +1092,41 @@ fn parse_primary<'a>( break; } - let (token, pos) = input.next().unwrap(); + let (token, token_pos) = input.next().unwrap(); root_expr = match (root_expr, token) { // Function call - (Expr::Variable(id, _, pos), Token::LeftParen) => { - parse_call_expr(input, stack, *id, pos, allow_stmt_expr)? + (Expr::Variable(id, modules, _, pos), Token::LeftParen) => { + parse_call_expr(input, stack, *id, modules, pos, allow_stmt_expr)? } (Expr::Property(id, pos), Token::LeftParen) => { - parse_call_expr(input, stack, id, pos, allow_stmt_expr)? + parse_call_expr(input, stack, id, None, pos, allow_stmt_expr)? + } + // module access + #[cfg(not(feature = "no_module"))] + (Expr::Variable(id, mut modules, mut index, pos), Token::DoubleColon) => { + match input.next().unwrap() { + (Token::Identifier(id2), pos2) => { + if let Some(ref mut modules) = modules { + modules.push((*id, pos)); + } else { + let mut vec = StaticVec::new(); + vec.push((*id, pos)); + modules = Some(Box::new(vec)); + + let root = modules.as_ref().unwrap().iter().next().unwrap(); + index = stack.find_sub_scope(&root.0); + } + + Expr::Variable(Box::new(id2), modules, index, pos2) + } + (_, pos2) => return Err(PERR::VariableExpected.into_err(pos2)), + } } // Indexing + #[cfg(not(feature = "no_index"))] (expr, Token::LeftBracket) => { - parse_index_chain(input, stack, expr, pos, allow_stmt_expr)? + parse_index_chain(input, stack, expr, token_pos, allow_stmt_expr)? } // Unknown postfix operator (expr, token) => panic!("unknown postfix operator {:?} for {:?}", token, expr), @@ -1069,6 +1182,7 @@ fn parse_unary<'a>( // Call negative function e => Ok(Expr::FnCall( Box::new("-".into()), + None, Box::new(vec![e]), None, pos, @@ -1085,6 +1199,7 @@ fn parse_unary<'a>( let pos = eat_token(input, Token::Bang); Ok(Expr::FnCall( Box::new("!".into()), + None, Box::new(vec![parse_primary(input, stack, allow_stmt_expr)?]), Some(Box::new(false.into())), // NOT operator, when operating on invalid operand, defaults to false pos, @@ -1138,25 +1253,34 @@ fn parse_op_assignment_stmt<'a>( // lhs op= rhs -> lhs = op(lhs, rhs) let args = vec![lhs_copy, rhs]; - let rhs_expr = Expr::FnCall(Box::new(op.into()), Box::new(args), None, pos); + let rhs_expr = Expr::FnCall(Box::new(op.into()), None, Box::new(args), None, pos); Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs_expr), pos)) } /// Make a dot expression. -fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position, is_index: bool) -> Expr { - match (lhs, rhs) { +fn make_dot_expr( + lhs: Expr, + rhs: Expr, + op_pos: Position, + is_index: bool, +) -> Result> { + Ok(match (lhs, rhs) { // idx_lhs[idx_rhs].rhs // Attach dot chain to the bottom level of indexing chain (Expr::Index(idx_lhs, idx_rhs, idx_pos), rhs) => Expr::Index( idx_lhs, - Box::new(make_dot_expr(*idx_rhs, rhs, op_pos, true)), + Box::new(make_dot_expr(*idx_rhs, rhs, op_pos, true)?), idx_pos, ), // lhs.id - (lhs, rhs @ Expr::Variable(_, _, _)) | (lhs, rhs @ Expr::Property(_, _)) => { + (lhs, rhs @ Expr::Variable(_, None, _, _)) | (lhs, rhs @ Expr::Property(_, _)) => { let lhs = if is_index { lhs.into_property() } else { lhs }; Expr::Dot(Box::new(lhs), Box::new(rhs.into_property()), op_pos) } + // lhs.module::id - syntax error + (_, Expr::Variable(_, Some(modules), _, _)) => { + return Err(PERR::PropertyExpected.into_err(modules.iter().next().unwrap().1)) + } // lhs.dot_lhs.dot_rhs (lhs, Expr::Dot(dot_lhs, dot_rhs, dot_pos)) => Expr::Dot( Box::new(lhs), @@ -1179,14 +1303,13 @@ fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position, is_index: bool) -> Expr ), // lhs.rhs (lhs, rhs) => Expr::Dot(Box::new(lhs), Box::new(rhs.into_property()), op_pos), - } + }) } /// Make an 'in' expression. fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result> { match (&lhs, &rhs) { (_, Expr::IntegerConstant(_, pos)) - | (_, Expr::FloatConstant(_, pos)) | (_, Expr::And(_, _, pos)) | (_, Expr::Or(_, _, pos)) | (_, Expr::In(_, _, pos)) @@ -1200,11 +1323,20 @@ fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result { + return Err(PERR::MalformedInExpr( + "'in' expression expects a string, array or object map".into(), + ) + .into_err(*pos)) + } + // "xxx" in "xxxx", 'x' in "xxxx" - OK! (Expr::StringConstant(_, _), Expr::StringConstant(_, _)) | (Expr::CharConstant(_, _), Expr::StringConstant(_, _)) => (), // 123.456 in "xxxx" + #[cfg(not(feature = "no_float"))] (Expr::FloatConstant(_, pos), Expr::StringConstant(_, _)) => { return Err(PERR::MalformedInExpr( "'in' expression for a string expects a string, not a float".into(), @@ -1258,6 +1390,7 @@ fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result (), // 123.456 in #{...} + #[cfg(not(feature = "no_float"))] (Expr::FloatConstant(_, pos), Expr::Map(_, _)) => { return Err(PERR::MalformedInExpr( "'in' expression for an object map expects a string, not a float".into(), @@ -1357,24 +1490,28 @@ fn parse_binary_op<'a>( current_lhs = match op_token { Token::Plus => Expr::FnCall( Box::new("+".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, ), Token::Minus => Expr::FnCall( Box::new("-".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, ), Token::Multiply => Expr::FnCall( Box::new("*".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, ), Token::Divide => Expr::FnCall( Box::new("/".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, @@ -1382,24 +1519,28 @@ fn parse_binary_op<'a>( Token::LeftShift => Expr::FnCall( Box::new("<<".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, ), Token::RightShift => Expr::FnCall( Box::new(">>".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, ), Token::Modulo => Expr::FnCall( Box::new("%".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, ), Token::PowerOf => Expr::FnCall( Box::new("~".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, @@ -1408,36 +1549,42 @@ fn parse_binary_op<'a>( // Comparison operators default to false when passed invalid operands Token::EqualsTo => Expr::FnCall( Box::new("==".into()), + None, Box::new(vec![current_lhs, rhs]), cmp_default, pos, ), Token::NotEqualsTo => Expr::FnCall( Box::new("!=".into()), + None, Box::new(vec![current_lhs, rhs]), cmp_default, pos, ), Token::LessThan => Expr::FnCall( Box::new("<".into()), + None, Box::new(vec![current_lhs, rhs]), cmp_default, pos, ), Token::LessThanEqualsTo => Expr::FnCall( Box::new("<=".into()), + None, Box::new(vec![current_lhs, rhs]), cmp_default, pos, ), Token::GreaterThan => Expr::FnCall( Box::new(">".into()), + None, Box::new(vec![current_lhs, rhs]), cmp_default, pos, ), Token::GreaterThanEqualsTo => Expr::FnCall( Box::new(">=".into()), + None, Box::new(vec![current_lhs, rhs]), cmp_default, pos, @@ -1447,18 +1594,21 @@ fn parse_binary_op<'a>( Token::And => Expr::And(Box::new(current_lhs), Box::new(rhs), pos), Token::Ampersand => Expr::FnCall( Box::new("&".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, ), Token::Pipe => Expr::FnCall( Box::new("|".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, ), Token::XOr => Expr::FnCall( Box::new("^".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, @@ -1467,9 +1617,9 @@ fn parse_binary_op<'a>( Token::In => make_in_expr(current_lhs, rhs, pos)?, #[cfg(not(feature = "no_object"))] - Token::Period => make_dot_expr(current_lhs, rhs, pos, false), + Token::Period => make_dot_expr(current_lhs, rhs, pos, false)?, - token => return Err(PERR::UnknownOperator(token.syntax().into()).into_err(pos)), + token => return Err(PERR::UnknownOperator(token.into()).into_err(pos)), }; } } @@ -1624,7 +1774,7 @@ fn parse_for<'a>( (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), (_, pos) => { return Err( - PERR::MissingToken("in".into(), "after the iteration variable".into()) + PERR::MissingToken(Token::In.into(), "after the iteration variable".into()) .into_err(pos), ) } @@ -1635,11 +1785,11 @@ fn parse_for<'a>( let expr = parse_expr(input, stack, allow_stmt_expr)?; let prev_len = stack.len(); - stack.push(name.clone()); + stack.push((name.clone(), ScopeEntryType::Normal)); let body = parse_block(input, stack, true, allow_stmt_expr)?; - stack.rewind(prev_len); + stack.truncate(prev_len); Ok(Stmt::For(Box::new(name), Box::new(expr), Box::new(body))) } @@ -1669,18 +1819,20 @@ fn parse_let<'a>( match var_type { // let name = expr ScopeEntryType::Normal => { - stack.push(name.clone()); + stack.push((name.clone(), ScopeEntryType::Normal)); Ok(Stmt::Let(Box::new(name), Some(Box::new(init_value)), pos)) } // const name = { expr:constant } ScopeEntryType::Constant if init_value.is_constant() => { - stack.push(name.clone()); + stack.push((name.clone(), ScopeEntryType::Constant)); Ok(Stmt::Const(Box::new(name), Box::new(init_value), pos)) } // const name = expr - error ScopeEntryType::Constant => { Err(PERR::ForbiddenConstantExpr(name).into_err(init_value.position())) } + // Variable cannot be a sub-scope + ScopeEntryType::Module => unreachable!(), } } else { // let name @@ -1688,6 +1840,40 @@ fn parse_let<'a>( } } +/// Parse an import statement. +fn parse_import<'a>( + input: &mut Peekable>, + stack: &mut Stack, + allow_stmt_expr: bool, +) -> Result> { + // import ... + let pos = eat_token(input, Token::Import); + + // import expr ... + let expr = parse_expr(input, stack, allow_stmt_expr)?; + + // import expr as ... + match input.next().unwrap() { + (Token::As, _) => (), + (_, pos) => { + return Err( + PERR::MissingToken(Token::As.into(), "in this import statement".into()) + .into_err(pos), + ) + } + } + + // import expr as name ... + let (name, _) = match input.next().unwrap() { + (Token::Identifier(s), pos) => (s, pos), + (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), + (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), + }; + + stack.push((name.clone(), ScopeEntryType::Module)); + Ok(Stmt::Import(Box::new(expr), Box::new(name), pos)) +} + /// Parse a statement block. fn parse_block<'a>( input: &mut Peekable>, @@ -1700,9 +1886,11 @@ fn parse_block<'a>( (Token::LeftBrace, pos) => pos, (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), (_, pos) => { - return Err( - PERR::MissingToken("{".into(), "to start a statement block".into()).into_err(pos), + return Err(PERR::MissingToken( + Token::LeftBrace.into(), + "to start a statement block".into(), ) + .into_err(pos)) } }; @@ -1739,15 +1927,16 @@ fn parse_block<'a>( // { ... stmt ??? (_, pos) => { // Semicolons are not optional between statements - return Err( - PERR::MissingToken(";".into(), "to terminate this statement".into()) - .into_err(*pos), - ); + return Err(PERR::MissingToken( + Token::SemiColon.into(), + "to terminate this statement".into(), + ) + .into_err(*pos)); } } } - stack.rewind(prev_len); + stack.truncate(prev_len); Ok(Stmt::Block(statements, pos)) } @@ -1782,7 +1971,6 @@ fn parse_stmt<'a>( Token::LeftBrace => parse_block(input, stack, breakable, allow_stmt_expr), // fn ... - #[cfg(not(feature = "no_function"))] Token::Fn => Err(PERR::WrongFnDefinition.into_err(*pos)), Token::If => parse_if(input, stack, breakable, allow_stmt_expr), @@ -1826,6 +2014,9 @@ fn parse_stmt<'a>( Token::Let => parse_let(input, stack, ScopeEntryType::Normal, allow_stmt_expr), Token::Const => parse_let(input, stack, ScopeEntryType::Constant, allow_stmt_expr), + #[cfg(not(feature = "no_module"))] + Token::Import => parse_import(input, stack, allow_stmt_expr), + _ => parse_expr_stmt(input, stack, allow_stmt_expr), } } @@ -1857,25 +2048,29 @@ fn parse_fn<'a>( loop { match input.next().unwrap() { (Token::Identifier(s), pos) => { - stack.push(s.clone()); + stack.push((s.clone(), ScopeEntryType::Normal)); params.push((s, pos)) } (Token::LexError(err), pos) => { return Err(PERR::BadInput(err.to_string()).into_err(pos)) } - (_, pos) => return Err(PERR::MissingToken(")".into(), end_err).into_err(pos)), + (_, pos) => { + return Err(PERR::MissingToken(Token::RightParen.into(), end_err).into_err(pos)) + } } match input.next().unwrap() { (Token::RightParen, _) => break, (Token::Comma, _) => (), (Token::Identifier(_), pos) => { - return Err(PERR::MissingToken(",".into(), sep_err).into_err(pos)) + return Err(PERR::MissingToken(Token::Comma.into(), sep_err).into_err(pos)) } (Token::LexError(err), pos) => { return Err(PERR::BadInput(err.to_string()).into_err(pos)) } - (_, pos) => return Err(PERR::MissingToken(",".into(), sep_err).into_err(pos)), + (_, pos) => { + return Err(PERR::MissingToken(Token::Comma.into(), sep_err).into_err(pos)) + } } } } @@ -1959,7 +2154,6 @@ fn parse_global_level<'a>( continue; } } - // Actual statement let stmt = parse_stmt(input, &mut stack, false, true)?; @@ -1985,10 +2179,11 @@ fn parse_global_level<'a>( // stmt ??? (_, pos) => { // Semicolons are not optional between statements - return Err( - PERR::MissingToken(";".into(), "to terminate this statement".into()) - .into_err(*pos), - ); + return Err(PERR::MissingToken( + Token::SemiColon.into(), + "to terminate this statement".into(), + ) + .into_err(*pos)); } } } diff --git a/src/result.rs b/src/result.rs index 17fe389a..5e38f8d9 100644 --- a/src/result.rs +++ b/src/result.rs @@ -29,7 +29,7 @@ pub enum EvalAltResult { /// /// Never appears under the `no_std` feature. #[cfg(not(feature = "no_std"))] - ErrorReadingScriptFile(PathBuf, std::io::Error), + ErrorReadingScriptFile(PathBuf, Position, std::io::Error), /// Call to an unknown function. Wrapped value is the name of the function. ErrorFunctionNotFound(String, Position), @@ -47,12 +47,14 @@ pub enum EvalAltResult { /// String indexing out-of-bounds. /// Wrapped values are the current number of characters in the string and the index number. ErrorStringBounds(usize, INT, Position), - /// Trying to index into a type that is not an array, an object map, or a string. + /// Trying to index into a type that is not an array, an object map, or a string, and has no indexer function defined. ErrorIndexingType(String, Position), /// Trying to index into an array or string with an index that is not `i64`. ErrorNumericIndexExpr(Position), /// Trying to index into a map with an index that is not `String`. ErrorStringIndexExpr(Position), + /// Trying to import with an expression that is not `String`. + ErrorImportExpr(Position), /// Invalid arguments for `in` operator. ErrorInExpr(Position), /// The guard expression in an `if` or `while` statement does not return a boolean value. @@ -61,6 +63,8 @@ pub enum EvalAltResult { ErrorFor(Position), /// Usage of an unknown variable. Wrapped value is the name of the variable. ErrorVariableNotFound(String, Position), + /// Usage of an unknown module. Wrapped value is the name of the module. + ErrorModuleNotFound(String, Position), /// Assignment to an inappropriate LHS (left-hand-side) expression. ErrorAssignmentToUnknownLHS(Position), /// Assignment to a constant variable. @@ -90,7 +94,7 @@ impl EvalAltResult { pub(crate) fn desc(&self) -> &str { match self { #[cfg(not(feature = "no_std"))] - Self::ErrorReadingScriptFile(_, _) => "Cannot read from script file", + Self::ErrorReadingScriptFile(_, _, _) => "Cannot read from script file", Self::ErrorParsing(p) => p.desc(), Self::ErrorFunctionNotFound(_, _) => "Function not found", @@ -104,8 +108,9 @@ impl EvalAltResult { } Self::ErrorStringIndexExpr(_) => "Indexing into an object map expects a string index", Self::ErrorIndexingType(_, _) => { - "Indexing can only be performed on an array, an object map, or a string" + "Indexing can only be performed on an array, an object map, a string, or a type with an indexer function defined" } + Self::ErrorImportExpr(_) => "Importing a module expects a string path", Self::ErrorArrayBounds(_, index, _) if *index < 0 => { "Array access expects non-negative index" } @@ -119,6 +124,7 @@ impl EvalAltResult { Self::ErrorLogicGuard(_) => "Boolean value expected", Self::ErrorFor(_) => "For loop expects an array, object map, or range", Self::ErrorVariableNotFound(_, _) => "Variable not found", + Self::ErrorModuleNotFound(_, _) => "module not found", Self::ErrorAssignmentToUnknownLHS(_) => { "Assignment to an unsupported left-hand side expression" } @@ -144,20 +150,26 @@ impl fmt::Display for EvalAltResult { match self { #[cfg(not(feature = "no_std"))] - Self::ErrorReadingScriptFile(path, err) => { + Self::ErrorReadingScriptFile(path, pos, err) if pos.is_none() => { write!(f, "{} '{}': {}", desc, path.display(), err) } + #[cfg(not(feature = "no_std"))] + Self::ErrorReadingScriptFile(path, pos, err) => { + write!(f, "{} '{}': {} ({})", desc, path.display(), err, pos) + } Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p), - Self::ErrorFunctionNotFound(s, pos) | Self::ErrorVariableNotFound(s, pos) => { - write!(f, "{}: '{}' ({})", desc, s, pos) - } + Self::ErrorFunctionNotFound(s, pos) + | Self::ErrorVariableNotFound(s, pos) + | Self::ErrorModuleNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), + Self::ErrorDotExpr(s, pos) if !s.is_empty() => write!(f, "{} {} ({})", desc, s, pos), Self::ErrorIndexingType(_, pos) | Self::ErrorNumericIndexExpr(pos) | Self::ErrorStringIndexExpr(pos) + | Self::ErrorImportExpr(pos) | Self::ErrorLogicGuard(pos) | Self::ErrorFor(pos) | Self::ErrorAssignmentToUnknownLHS(pos) @@ -253,7 +265,7 @@ impl EvalAltResult { pub fn position(&self) -> Position { match self { #[cfg(not(feature = "no_std"))] - Self::ErrorReadingScriptFile(_, _) => Position::none(), + Self::ErrorReadingScriptFile(_, pos, _) => *pos, Self::ErrorParsing(err) => err.position(), @@ -266,9 +278,11 @@ impl EvalAltResult { | Self::ErrorIndexingType(_, pos) | Self::ErrorNumericIndexExpr(pos) | Self::ErrorStringIndexExpr(pos) + | Self::ErrorImportExpr(pos) | Self::ErrorLogicGuard(pos) | Self::ErrorFor(pos) | Self::ErrorVariableNotFound(_, pos) + | Self::ErrorModuleNotFound(_, pos) | Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToConstant(_, pos) | Self::ErrorMismatchOutputType(_, pos) @@ -287,7 +301,7 @@ impl EvalAltResult { pub(crate) fn set_position(mut err: Box, new_position: Position) -> Box { match err.as_mut() { #[cfg(not(feature = "no_std"))] - Self::ErrorReadingScriptFile(_, _) => (), + Self::ErrorReadingScriptFile(_, pos, _) => *pos = new_position, Self::ErrorParsing(err) => err.1 = new_position, @@ -300,9 +314,11 @@ impl EvalAltResult { | Self::ErrorIndexingType(_, pos) | Self::ErrorNumericIndexExpr(pos) | Self::ErrorStringIndexExpr(pos) + | Self::ErrorImportExpr(pos) | Self::ErrorLogicGuard(pos) | Self::ErrorFor(pos) | Self::ErrorVariableNotFound(_, pos) + | Self::ErrorModuleNotFound(_, pos) | Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToConstant(_, pos) | Self::ErrorMismatchOutputType(_, pos) diff --git a/src/scope.rs b/src/scope.rs index b50d1af3..2411dcc1 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,10 +1,13 @@ //! Module that defines the `Scope` type representing a function call-stack scope. -use crate::any::{Dynamic, Variant}; +use crate::any::{Dynamic, Union, Variant}; use crate::parser::{map_dynamic_to_expr, Expr}; use crate::token::Position; -use crate::stdlib::{borrow::Cow, boxed::Box, iter, vec::Vec}; +#[cfg(not(feature = "no_module"))] +use crate::module::Module; + +use crate::stdlib::{borrow::Cow, boxed::Box, iter, vec, vec::Vec}; /// Type of an entry in the Scope. #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] @@ -13,6 +16,9 @@ pub enum EntryType { Normal, /// Immutable constant value. Constant, + /// Name of a module, allowing member access with the :: operator. + /// This is for internal use only. + Module, } /// An entry in the Scope. @@ -56,7 +62,7 @@ pub struct Entry<'a> { /// allowing for automatic _shadowing_. /// /// Currently, `Scope` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Scope<'a>(Vec>); impl<'a> Scope<'a> { @@ -73,7 +79,7 @@ impl<'a> Scope<'a> { /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` pub fn new() -> Self { - Self(Vec::new()) + Default::default() } /// Empty the Scope. @@ -165,6 +171,19 @@ impl<'a> Scope<'a> { self.push_dynamic_value(name, EntryType::Normal, value, false); } + /// Add (push) a new module to the Scope. + /// + /// Modules are used for accessing member variables, functions and plugins under a namespace. + #[cfg(not(feature = "no_module"))] + pub fn push_module>>(&mut self, name: K, value: Module) { + self.push_dynamic_value( + name, + EntryType::Module, + Dynamic(Union::Module(Box::new(value))), + false, + ); + } + /// Add (push) a new constant to the Scope. /// /// Constants are immutable and cannot be assigned to. Their values never change. @@ -279,26 +298,61 @@ impl<'a> Scope<'a> { self.0 .iter() .rev() // Always search a Scope in reverse order - .any(|Entry { name: key, .. }| name == key) + .any(|Entry { name: key, typ, .. }| match typ { + EntryType::Normal | EntryType::Constant => name == key, + EntryType::Module => false, + }) } /// Find an entry in the Scope, starting from the last. - pub(crate) fn get(&self, name: &str) -> Option<(usize, EntryType)> { + /// + /// modules are ignored. + pub(crate) fn get_index(&self, name: &str) -> Option<(usize, EntryType)> { self.0 .iter() .enumerate() .rev() // Always search a Scope in reverse order - .find_map(|(index, Entry { name: key, typ, .. })| { - if name == key { - Some((index, *typ)) - } else { - None + .find_map(|(index, Entry { name: key, typ, .. })| match typ { + EntryType::Normal | EntryType::Constant => { + if name == key { + Some((index, *typ)) + } else { + None + } } + EntryType::Module => None, }) } + /// Find a module in the Scope, starting from the last. + pub(crate) fn get_module_index(&self, name: &str) -> Option { + self.0 + .iter() + .enumerate() + .rev() // Always search a Scope in reverse order + .find_map(|(index, Entry { name: key, typ, .. })| match typ { + EntryType::Module => { + if name == key { + Some(index) + } else { + None + } + } + EntryType::Normal | EntryType::Constant => None, + }) + } + + /// Find a module in the Scope, starting from the last entry. + #[cfg(not(feature = "no_module"))] + pub fn find_module(&mut self, name: &str) -> Option<&mut Module> { + let index = self.get_module_index(name)?; + self.get_mut(index).0.downcast_mut::() + } + /// Get the value of an entry in the Scope, starting from the last. /// + /// modules are ignored. + /// /// # Examples /// /// ``` @@ -313,7 +367,10 @@ impl<'a> Scope<'a> { self.0 .iter() .rev() - .find(|Entry { name: key, .. }| name == key) + .find(|Entry { name: key, typ, .. }| match typ { + EntryType::Normal | EntryType::Constant => name == key, + EntryType::Module => false, + }) .and_then(|Entry { value, .. }| value.downcast_ref::().cloned()) } @@ -339,12 +396,14 @@ impl<'a> Scope<'a> { /// assert_eq!(my_scope.get_value::("x").unwrap(), 0); /// ``` pub fn set_value(&mut self, name: &'a str, value: T) { - match self.get(name) { + match self.get_index(name) { + None => self.push(name, value), Some((_, EntryType::Constant)) => panic!("variable {} is constant", name), Some((index, EntryType::Normal)) => { self.0.get_mut(index).unwrap().value = Dynamic::from(value) } - None => self.push(name, value), + // modules cannot be modified + Some((_, EntryType::Module)) => unreachable!(), } } @@ -361,18 +420,17 @@ impl<'a> Scope<'a> { (&mut entry.value, entry.typ) } + /// Get an iterator to entries in the Scope. + pub(crate) fn into_iter(self) -> impl Iterator> { + self.0.into_iter() + } + /// Get an iterator to entries in the Scope. pub(crate) fn iter(&self) -> impl Iterator { self.0.iter().rev() // Always search a Scope in reverse order } } -impl Default for Scope<'_> { - fn default() -> Self { - Scope::new() - } -} - impl<'a, K: Into>> iter::Extend<(K, EntryType, Dynamic)> for Scope<'a> { fn extend>(&mut self, iter: T) { self.0 diff --git a/src/token.rs b/src/token.rs index f202bccd..f67a8e14 100644 --- a/src/token.rs +++ b/src/token.rs @@ -122,7 +122,7 @@ impl fmt::Display for Position { impl fmt::Debug for Position { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "({}:{})", self.line, self.pos) + write!(f, "{}:{}", self.line, self.pos) } } @@ -153,9 +153,9 @@ pub enum Token { RightShift, SemiColon, Colon, + DoubleColon, Comma, Period, - #[cfg(not(feature = "no_object"))] MapStart, Equals, True, @@ -180,7 +180,6 @@ pub enum Token { XOr, Ampersand, And, - #[cfg(not(feature = "no_function"))] Fn, Continue, Break, @@ -197,6 +196,9 @@ pub enum Token { XOrAssign, ModuloAssign, PowerOfAssign, + Import, + Export, + As, LexError(Box), EOF, } @@ -230,9 +232,9 @@ impl Token { Divide => "/", SemiColon => ";", Colon => ":", + DoubleColon => "::", Comma => ",", Period => ".", - #[cfg(not(feature = "no_object"))] MapStart => "#{", Equals => "=", True => "true", @@ -243,6 +245,8 @@ impl Token { Else => "else", While => "while", Loop => "loop", + For => "for", + In => "in", LessThan => "<", GreaterThan => ">", Bang => "!", @@ -254,7 +258,6 @@ impl Token { Or => "||", Ampersand => "&", And => "&&", - #[cfg(not(feature = "no_function"))] Fn => "fn", Continue => "continue", Break => "break", @@ -276,8 +279,9 @@ impl Token { ModuloAssign => "%=", PowerOf => "~", PowerOfAssign => "~=", - For => "for", - In => "in", + Import => "import", + Export => "export", + As => "as", EOF => "{EOF}", _ => panic!("operator should be match in outer scope"), }) @@ -406,6 +410,12 @@ impl Token { } } +impl From for String { + fn from(token: Token) -> Self { + token.syntax().into() + } +} + /// An iterator on a `Token` stream. pub struct TokenIterator<'a> { /// Can the next token be a unary operator? @@ -741,6 +751,13 @@ impl<'a> TokenIterator<'a> { "for" => Token::For, "in" => Token::In, + #[cfg(not(feature = "no_module"))] + "import" => Token::Import, + #[cfg(not(feature = "no_module"))] + "export" => Token::Export, + #[cfg(not(feature = "no_module"))] + "as" => Token::As, + #[cfg(not(feature = "no_function"))] "fn" => Token::Fn, @@ -874,7 +891,6 @@ impl<'a> TokenIterator<'a> { ('/', _) => return Some((Token::Divide, pos)), (';', _) => return Some((Token::SemiColon, pos)), - (':', _) => return Some((Token::Colon, pos)), (',', _) => return Some((Token::Comma, pos)), ('.', _) => return Some((Token::Period, pos)), @@ -896,6 +912,13 @@ impl<'a> TokenIterator<'a> { } ('=', _) => return Some((Token::Equals, pos)), + #[cfg(not(feature = "no_module"))] + (':', ':') => { + self.eat_next(); + return Some((Token::DoubleColon, pos)); + } + (':', _) => return Some((Token::Colon, pos)), + ('<', '=') => { self.eat_next(); return Some((Token::LessThanEqualsTo, pos)); diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 00000000..4a9479f1 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,128 @@ +//! Module containing various utility types and functions. + +use crate::stdlib::{ + any::TypeId, + fmt, + hash::{Hash, Hasher}, + mem, + vec::Vec, +}; + +#[cfg(not(feature = "no_std"))] +use crate::stdlib::collections::hash_map::DefaultHasher; + +#[cfg(feature = "no_std")] +use ahash::AHasher; + +/// Calculate a `u64` hash key from a function name and parameter types. +/// +/// Parameter types are passed in via `TypeId` values from an iterator +/// which can come from any source. +pub fn calc_fn_spec(fn_name: &str, params: impl Iterator) -> u64 { + #[cfg(feature = "no_std")] + let mut s: AHasher = Default::default(); + #[cfg(not(feature = "no_std"))] + let mut s = DefaultHasher::new(); + + s.write(fn_name.as_bytes()); + params.for_each(|t| t.hash(&mut s)); + s.finish() +} + +/// Calculate a `u64` hash key from a function name and number of parameters (without regard to types). +pub(crate) fn calc_fn_def(fn_name: &str, num_params: usize) -> u64 { + #[cfg(feature = "no_std")] + let mut s: AHasher = Default::default(); + #[cfg(not(feature = "no_std"))] + let mut s = DefaultHasher::new(); + + s.write(fn_name.as_bytes()); + s.write_usize(num_params); + s.finish() +} + +/// A type to hold a number of values in static storage for speed, and any spill-overs in a `Vec`. +/// +/// This is essentially a knock-off of the [`staticvec`](https://crates.io/crates/staticvec) crate. +/// This simplified implementation here is to avoid pulling in another crate. +#[derive(Clone, Default)] +pub struct StaticVec { + /// Total number of values held. + len: usize, + /// Static storage. 4 slots should be enough for most cases - i.e. four levels of indirection. + list: [T; 4], + /// Dynamic storage. For spill-overs. + more: Vec, +} + +impl StaticVec { + /// Create a new `StaticVec`. + pub fn new() -> Self { + Default::default() + } + /// Push a new value to the end of this `StaticVec`. + pub fn push>(&mut self, value: X) { + if self.len >= self.list.len() { + self.more.push(value.into()); + } else { + self.list[self.len] = value.into(); + } + self.len += 1; + } + /// Pop a value from the end of this `StaticVec`. + /// + /// # Panics + /// + /// Panics if the `StaticVec` is empty. + pub fn pop(&mut self) -> T { + let result = if self.len <= 0 { + panic!("nothing to pop!") + } else if self.len <= self.list.len() { + mem::take(self.list.get_mut(self.len - 1).unwrap()) + } else { + self.more.pop().unwrap() + }; + + self.len -= 1; + + result + } + /// Get the number of items in this `StaticVec`. + pub fn len(&self) -> usize { + self.len + } + /// Get an item at a particular index. + /// + /// # Panics + /// + /// Panics if the index is out of bounds. + pub fn get(&self, index: usize) -> &T { + if index >= self.len { + panic!("index OOB in StaticVec"); + } + + if index < self.list.len() { + self.list.get(index).unwrap() + } else { + self.more.get(index - self.list.len()).unwrap() + } + } + /// Get an iterator to entries in the `StaticVec`. + pub fn iter(&self) -> impl Iterator { + let num = if self.len >= self.list.len() { + self.list.len() + } else { + self.len + }; + + self.list[..num].iter().chain(self.more.iter()) + } +} + +impl fmt::Debug for StaticVec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[ ")?; + self.iter().try_for_each(|v| write!(f, "{:?}, ", v))?; + write!(f, "]") + } +} diff --git a/tests/get_set.rs b/tests/get_set.rs index 5859ea2e..57712304 100644 --- a/tests/get_set.rs +++ b/tests/get_set.rs @@ -7,6 +7,8 @@ fn test_get_set() -> Result<(), Box> { #[derive(Clone)] struct TestStruct { x: INT, + y: INT, + array: Vec, } impl TestStruct { @@ -18,8 +20,16 @@ fn test_get_set() -> Result<(), Box> { self.x = new_x; } + fn get_y(&mut self) -> INT { + self.y + } + fn new() -> Self { - TestStruct { x: 1 } + TestStruct { + x: 1, + y: 0, + array: vec![1, 2, 3, 4, 5], + } } } @@ -28,9 +38,19 @@ fn test_get_set() -> Result<(), Box> { engine.register_type::(); engine.register_get_set("x", TestStruct::get_x, TestStruct::set_x); + engine.register_get("y", TestStruct::get_y); + engine.register_fn("add", |value: &mut INT| *value += 41); engine.register_fn("new_ts", TestStruct::new); + #[cfg(not(feature = "no_index"))] + engine.register_indexer(|value: &mut TestStruct, index: INT| value.array[index as usize]); + assert_eq!(engine.eval::("let a = new_ts(); a.x = 500; a.x")?, 500); + assert_eq!(engine.eval::("let a = new_ts(); a.x.add(); a.x")?, 42); + assert_eq!(engine.eval::("let a = new_ts(); a.y.add(); a.y")?, 0); + + #[cfg(not(feature = "no_index"))] + assert_eq!(engine.eval::("let a = new_ts(); a[3]")?, 4); Ok(()) } diff --git a/tests/modules.rs b/tests/modules.rs new file mode 100644 index 00000000..e6028179 --- /dev/null +++ b/tests/modules.rs @@ -0,0 +1,83 @@ +#![cfg(not(feature = "no_module"))] +use rhai::{module_resolvers, Engine, EvalAltResult, Module, Scope, INT}; + +#[test] +fn test_module() { + let mut module = Module::new(); + module.set_var("answer", 42 as INT); + + assert!(module.contains_var("answer")); + assert_eq!(module.get_var_value::("answer").unwrap(), 42); +} + +#[test] +fn test_module_sub_module() -> Result<(), Box> { + let mut module = Module::new(); + + let mut sub_module = Module::new(); + + let mut sub_module2 = Module::new(); + sub_module2.set_var("answer", 41 as INT); + let hash = sub_module2.set_fn_1("inc", |x: INT| Ok(x + 1)); + + sub_module.set_sub_module("universe", sub_module2); + module.set_sub_module("life", sub_module); + + assert!(module.contains_sub_module("life")); + let m = module.get_sub_module("life").unwrap(); + + assert!(m.contains_sub_module("universe")); + let m2 = m.get_sub_module("universe").unwrap(); + + assert!(m2.contains_var("answer")); + assert!(m2.contains_fn(hash)); + + assert_eq!(m2.get_var_value::("answer").unwrap(), 41); + + let mut engine = Engine::new(); + let mut scope = Scope::new(); + + scope.push_module("question", module); + + assert_eq!( + engine.eval_expression_with_scope::( + &mut scope, + "question::life::universe::answer + 1" + )?, + 42 + ); + assert_eq!( + engine.eval_expression_with_scope::( + &mut scope, + "question::life::universe::inc(question::life::universe::answer)" + )?, + 42 + ); + + Ok(()) +} + +#[test] +fn test_module_resolver() -> Result<(), Box> { + let mut resolver = module_resolvers::StaticModuleResolver::new(); + + let mut module = Module::new(); + module.set_var("answer", 42 as INT); + + resolver.insert("hello".to_string(), module); + + let mut engine = Engine::new(); + engine.set_module_resolver(Some(resolver)); + + assert_eq!( + engine.eval::( + r#" + import "hello" as h; + h::answer + "# + )?, + 42 + ); + + Ok(()) +}