Merge pull request #293 from schungx/master

Add Engine::register_module.
This commit is contained in:
Stephen Chung 2020-11-16 16:54:18 +08:00 committed by GitHub
commit b21d5e6cee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 1474 additions and 780 deletions

View File

@ -10,6 +10,12 @@ New features
------------ ------------
* `switch` statement. * `switch` statement.
* `Engine::register_module` to register a module as a sub-module in the global namespace, while at the same time exposing its method functions globally. This is convenient when registering an API for a custom type.
Enhancements
------------
* New constant `Dynamic::UNIT`.
Version 0.19.5 Version 0.19.5

View File

@ -156,3 +156,61 @@ fn bench_eval_loop_strings_no_build(bench: &mut Bencher) {
bench.iter(|| engine.consume_ast(&ast).unwrap()); bench.iter(|| engine.consume_ast(&ast).unwrap());
} }
#[bench]
fn bench_eval_switch(bench: &mut Bencher) {
let script = r#"
let sum = 0;
let rem = 0;
for x in range(0, 10) {
rem = x % 5;
sum += switch rem {
0 => 10,
1 => 12,
2 => 42,
3 => 1,
_ => 9
}
}
"#;
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap());
}
#[bench]
fn bench_eval_nested_if(bench: &mut Bencher) {
let script = r#"
let sum = 0;
let rem = 0;
for x in range(0, 10) {
rem = x % 5;
sum += if rem == 0 {
10
} else if rem == 1 {
12
} else if rem == 2 {
42
} else if rem == 3 {
1
} else{
9
};
}
"#;
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap());
}

View File

@ -259,7 +259,7 @@ impl Module {
let mut mod_all = mod_all.unwrap(); let mut mod_all = mod_all.unwrap();
let mod_name = mod_all.ident.clone(); let mod_name = mod_all.ident.clone();
let (_, orig_content) = mod_all.content.take().unwrap(); let (_, orig_content) = mod_all.content.take().unwrap();
let mod_attrs = mem::replace(&mut mod_all.attrs, Vec::with_capacity(0)); let mod_attrs = mem::take(&mut mod_all.attrs);
if !params.skip { if !params.skip {
// Generate new module items. // Generate new module items.

View File

@ -117,13 +117,14 @@ The Rhai Scripting Language
6. [Subtle Semantic Changes](engine/optimize/semantics.md) 6. [Subtle Semantic Changes](engine/optimize/semantics.md)
8. [Usage Patterns](patterns/index.md) 8. [Usage Patterns](patterns/index.md)
1. [Object-Oriented Programming (OOP)](patterns/oop.md) 1. [Object-Oriented Programming (OOP)](patterns/oop.md)
2. [Loadable Configuration](patterns/config.md) 2. [Working With Rust Enums](patterns/enums.md)
3. [Control Layer](patterns/control.md) 3. [Loadable Configuration](patterns/config.md)
4. [Singleton Command](patterns/singleton.md) 4. [Control Layer](patterns/control.md)
5. [Multi-Layer Functions](patterns/multi-layer.md) 5. [Singleton Command](patterns/singleton.md)
6. [One Engine Instance Per Call](patterns/parallel.md) 6. [Multi-Layer Functions](patterns/multi-layer.md)
7. [Scriptable Event Handler with State](patterns/events.md) 7. [One Engine Instance Per Call](patterns/parallel.md)
8. [Dynamic Constants Provider](patterns/dynamic-const.md) 8. [Scriptable Event Handler with State](patterns/events.md)
9. [Dynamic Constants Provider](patterns/dynamic-const.md)
9. [Advanced Topics](advanced.md) 9. [Advanced Topics](advanced.md)
1. [Capture Scope for Function Call](language/fn-capture.md) 1. [Capture Scope for Function Call](language/fn-capture.md)
2. [Low-Level API](rust/register-raw.md) 2. [Low-Level API](rust/register-raw.md)

View File

@ -94,7 +94,7 @@ The following _precedence table_ shows the built-in precedence of standard Rhai
| ------------------- | :-------------------------------------------------------------------------------------: | :----------------: | | ------------------- | :-------------------------------------------------------------------------------------: | :----------------: |
| Assignments | `=`, `+=`, `-=`, `*=`, `/=`, `~=`, `%=`,<br/>`<<=`, `>>=`, `&=`, <code>\|=</code>, `^=` | 0 | | Assignments | `=`, `+=`, `-=`, `*=`, `/=`, `~=`, `%=`,<br/>`<<=`, `>>=`, `&=`, <code>\|=</code>, `^=` | 0 |
| Logic and bit masks | <code>\|\|</code>, <code>\|</code>, `^` | 30 | | Logic and bit masks | <code>\|\|</code>, <code>\|</code>, `^` | 30 |
| Logic and bit masks | `&`, `&&` | 60 | | Logic and bit masks | `&&`, `&` | 60 |
| Comparisons | `==`, `!=` | 90 | | Comparisons | `==`, `!=` | 90 |
| | `in` | 110 | | | `in` | 110 |
| Comparisons | `>`, `>=`, `<`, `<=` | 130 | | Comparisons | `>`, `>=`, `<`, `<=` | 130 |

View File

@ -216,7 +216,7 @@ fn implementation_func(
} }
} }
Ok(().into()) Ok(Dynamic::UNIT)
} }
// Register the custom syntax (sample): exec |x| -> { x += 1 } while x < 0; // Register the custom syntax (sample): exec |x| -> { x += 1 } while x < 0;

View File

@ -71,20 +71,25 @@ switch map {
} }
``` ```
Switching on [arrays] is very useful when working with Rust enums (see [this chapter]({{rootUrl}}/patterns/enums.md)
for more details).
Difference From If-Else Chain
-----------------------------
Although a `switch` expression looks _almost_ the same as an `if`-`else` chain, Difference From `if` - `else if` Chain
-------------------------------------
Although a `switch` expression looks _almost_ the same as an `if`-`else if` chain,
there are subtle differences between the two. there are subtle differences between the two.
### Look-up Table vs `x == y`
A `switch` expression matches through _hashing_ via a look-up table. A `switch` expression matches through _hashing_ via a look-up table.
Therefore, matching is very fast. Walking down an `if`-`else` chain Therefore, matching is very fast. Walking down an `if`-`else if` chain
will be _much_ slower. is _much_ slower.
On the other hand, operators can be [overloaded][operator overloading] in Rhai, On the other hand, operators can be [overloaded][operator overloading] in Rhai,
meaning that it is possible to override the `==` operator for integers such meaning that it is possible to override the `==` operator for integers such
that `if x == y` returns a different result from the built-in default. that `x == y` returns a different result from the built-in default.
`switch` expressions do _not_ use the `==` operator for comparison; `switch` expressions do _not_ use the `==` operator for comparison;
instead, they _hash_ the data values and jump directly to the correct instead, they _hash_ the data values and jump directly to the correct
@ -95,3 +100,12 @@ the `==` operator will have no effect.
Therefore, in environments where it is desirable to [overload][operator overloading] Therefore, in environments where it is desirable to [overload][operator overloading]
the `==` operator - though it is difficult to think of valid scenarios where you'd want the `==` operator - though it is difficult to think of valid scenarios where you'd want
`1 == 1` to return something other than `true` - avoid using the `switch` expression. `1 == 1` to return something other than `true` - avoid using the `switch` expression.
### Efficiency
Because the `switch` expression works through a look-up table, it is very efficient
even for _large_ number of cases; in fact, switching is an O(1) operation regardless
of the size of the data and number of cases to match.
A long `if`-`else if` chain becomes increasingly slower with each additional case
because essentially an O(n) _linear scan_ is performed.

View File

@ -118,7 +118,7 @@ engine.register_result_fn("bunny_set_speed", move |speed: i64|
return Err("Bunny is not yet going!".into()); return Err("Bunny is not yet going!".into());
} }
Ok(().into()) Ok(Dynamic::UNIT)
); );
``` ```

229
doc/src/patterns/enums.md Normal file
View File

@ -0,0 +1,229 @@
Working With Rust Enums
=======================
{{#include ../links.md}}
Enums in Rust are typically used with _pattern matching_. Rhai is dynamic, so although
it integrates with Rust enum variants just fine (treated transparently as [custom types]),
it is impossible (short of registering a complete API) to distinguish between individual
enum variants or to extract internal data from them.
Simulate an Enum API
--------------------
A [plugin module] is extremely handy in creating an entire API for a custom enum type.
```rust
use rhai::{Engine, Dynamic, EvalAltResult};
use rhai::plugin::*;
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
enum MyEnum {
Foo,
Bar(i64),
Baz(String, bool)
}
// Create a plugin module with functions constructing the 'MyEnum' variants
#[export_module]
mod MyEnumModule {
// Constructors for 'MyEnum' variants
pub const Foo: &MyEnum = MyEnum::Foo;
pub fn Bar(value: i64) -> MyEnum {
MyEnum::Bar(value)
}
pub fn Baz(val1: String, val2: bool) -> MyEnum {
MyEnum::Baz(val1, val2)
}
// Access to fields
#[rhai_fn(get = "enum_type")]
pub fn get_type(a: &mut MyEnum) -> String {
match a {
MyEnum::Foo => "Foo".to_string(),
MyEnum::Bar(_) => "Bar".to_string(),
MyEnum::Baz(_, _) => "Baz".to_string()
}
}
#[rhai_fn(get = "field_0")]
pub fn get_field_0(a: &mut MyEnum) -> Dynamic {
match a {
MyEnum::Foo => Dynamic::UNIT,
MyEnum::Bar(x) => Dynamic::from(x),
MyEnum::Baz(x, _) => Dynamic::from(x)
}
}
#[rhai_fn(get = "field_1")]
pub fn get_field_1(a: &mut MyEnum) -> Dynamic {
match a {
MyEnum::Foo | MyEnum::Bar(_) => Dynamic::UNIT,
MyEnum::Baz(_, x) => Dynamic::from(x)
}
}
// Printing
#[rhai(name = "to_string", name = "print", name = "debug")]
pub fn to_string(a: &mut MyEnum) -> String {
format!("{:?}", a))
}
#[rhai_fn(name = "+")]
pub fn add_to_str(s: &str, a: MyEnum) -> String {
format!("{}{:?}", s, a))
}
#[rhai_fn(name = "+")]
pub fn add_str(a: &mut MyEnum, s: &str) -> String {
format!("{:?}", a).push_str(s))
}
#[rhai_fn(name = "+=")]
pub fn append_to_str(s: &mut ImmutableString, a: MyEnum) -> String {
s += a.to_string())
}
// '==' and '!=' operators
#[rhai_fn(name = "==")]
pub fn eq(a: &mut MyEnum, b: MyEnum) -> bool {
a == &b
}
#[rhai_fn(name = "!=")]
pub fn neq(a: &mut MyEnum, b: MyEnum) -> bool {
a != &b
}
// Array functions
#[rhai_fn(name = "push")]
pub fn append_to_array(list: &mut Array, item: MyEnum) {
list.push(Dynamic::from(item)));
}
#[rhai_fn(name = "+=")]
pub fn append_to_array_op(list: &mut Array, item: MyEnum) {
list.push(Dynamic::from(item)));
}
#[rhai_fn(name = "insert")]
pub fn insert_to_array(list: &mut Array, position: i64, item: MyEnum) {
if position <= 0 {
list.insert(0, Dynamic::from(item));
} else if (position as usize) >= list.len() - 1 {
list.push(item);
} else {
list.insert(position as usize, Dynamic::from(item));
}
}
#[rhai_fn(name = "pad")]
pub fn pad_array(list: &mut Array, len: i64, item: MyEnum) {
if len as usize > list.len() { list.resize(len as usize, item); }
}
}
let mut engine = Engine::new();
// Load the module as the module namespace "MyEnum"
engine
.register_type_with_name::<MyEnum>("MyEnum")
.register_module("MyEnum", exported_module!(MyEnumModule));
```
With this API in place, working with enums feels almost the same as in Rust:
```rust
let x = MyEnum::Foo;
let y = MyEnum::Bar(42);
let z = MyEnum::Baz("hello", true);
x == MyEnum::Foo;
y != MyEnum::Bar(0);
// Detect enum types
x.enum_type == "Foo";
y.enum_type == "Bar";
z.enum_type == "Baz";
// Extract enum fields
y.field_0 == 42;
y.field_1 == ();
z.field_0 == "hello";
z.field_1 == true;
```
Since enums are internally treated as [custom types], they are not _literals_ and cannot be
used as a match case in `switch` expressions. This is quite a limitation because the equivalent
`match` statement is commonly used in Rust to work with enums and bind variables to
variant-internal data.
It is possible, however, to `switch` through enum variants based on their types:
```c
switch x.enum_type {
"Foo" => ...,
"Bar" => {
let value = foo.field_0;
...
}
"Baz" => {
let val1 = foo.field_0;
let val2 = foo.field_1;
...
}
}
```
Use `switch` Through Arrays
---------------------------
Another way to work with Rust enums in a `switch` expression is through exposing the internal data
of each enum variant as a variable-length [array], usually with the name of the variant as
the first item for convenience:
```rust
use rhai::Array;
engine.register_get("enum_data", |x: &mut Enum| {
match x {
Enum::Foo => vec![
"Foo".into()
] as Array,
Enum::Bar(value) => vec![
"Bar".into(), (*value).into()
] as Array,
Enum::Baz(val1, val2) => vec![
"Baz".into(), val1.clone().into(), (*val2).into()
] as Array
}
});
```
Then it is a simple matter to match an enum via the `switch` expression:
```c
// Assume 'value' = 'MyEnum::Baz("hello", true)'
// 'enum_data' creates a variable-length array with 'MyEnum' data
let x = switch value.enum_data {
["Foo"] => 1,
["Bar", 42] => 2,
["Bar", 123] => 3,
["Baz", "hello", false] => 4,
["Baz", "hello", true] => 5,
_ => 9
};
x == 5;
// Which is essentially the same as:
let x = switch [value.type, value.field_0, value.field_1] {
["Foo", (), ()] => 1,
["Bar", 42, ()] => 2,
["Bar", 123, ()] => 3,
["Baz", "hello", false] => 4,
["Baz", "hello", true] => 5,
_ => 9
}
```

View File

@ -137,7 +137,7 @@ impl Handler {
// Default implementation of 'update' event handler // Default implementation of 'update' event handler
self.scope.set_value("state2", SomeType::new(42)); self.scope.set_value("state2", SomeType::new(42));
// Turn function-not-found into a success // Turn function-not-found into a success
Ok(().into()) Ok(Dynamic::UNIT)
} }
_ => Err(err.into()) _ => Err(err.into())
}) })

View File

@ -131,7 +131,7 @@ pub mod bunny_api {
Err("Bunny is not yet going!".into()) Err("Bunny is not yet going!".into())
} else { } else {
b.borrow_mut().set_speed(speed); b.borrow_mut().set_speed(speed);
Ok(().into()) Ok(Dynamic::UNIT)
} }
} }
pub fn turn_left(bunny: &mut SharedBunny) { pub fn turn_left(bunny: &mut SharedBunny) {

View File

@ -123,6 +123,53 @@ x == 43;
Notice that, when using a [module] as a [package], only functions registered at the _top level_ Notice that, when using a [module] as a [package], only functions registered at the _top level_
can be accessed. Variables as well as sub-modules are ignored. can be accessed. Variables as well as sub-modules are ignored.
### Use `Engine::register_module`
Another simple way to load this into an [`Engine`] is, again, to use the `exported_module!` macro
to turn it into a normal Rhai [module], then use the `Engine::register_module` method on it:
```rust
fn main() {
let mut engine = Engine::new();
// The macro call creates a Rhai module from the plugin module.
let module = exported_module!(my_module);
// A module can simply be loaded as a globally-available module.
engine.register_module("service", module);
}
```
The functions contained within the module definition (i.e. `greet`, `get_num` and `increment`),
plus the constant `MY_NUMBER`, are automatically loaded under the module namespace `service`:
```rust
let x = service::greet("world");
x == "hello, world!";
service::MY_NUMBER == 42;
let x = service::greet(service::get_num().to_string());
x == "hello, 42!";
let x = service::get_num();
x == 42;
service::increment(x);
x == 43;
```
`Engine::register_module` also exposes all _methods_ and _iterators_ from the module to the
_global_ namespace, so [getters/setters] and [indexers] for [custom types] work as expected.
Therefore, in the example able, `increment` works fine when called in method-call style:
```rust
let x = 42;
x.increment();
x == 43;
```
### Use as loadable `Module` ### Use as loadable `Module`
Using this directly as a dynamically-loadable Rhai [module] is almost the same, except that a Using this directly as a dynamically-loadable Rhai [module] is almost the same, except that a

View File

@ -191,3 +191,10 @@ engine.register_fn("==",
let item = new_ts(); // construct a new 'TestStruct' let item = new_ts(); // construct a new 'TestStruct'
item in array; // 'in' operator uses '==' item in array; // 'in' operator uses '=='
``` ```
Working With Enums
------------------
It is quite easy to use Rust enums with Rhai.
See [this chapter]({{rootUrl}}/patterns/enums.md) for more details.

View File

@ -25,7 +25,8 @@ Make the `Module` Available to the `Engine`
`Engine::load_package` supports loading a [module] as a [package]. `Engine::load_package` supports loading a [module] as a [package].
Since it acts as a [package], all functions will be registered into the _global_ namespace Since it acts as a [package], all functions will be registered into the _global_ namespace
and can be accessed without _module qualifiers_. and can be accessed without _namespace qualifiers_. This is by far the easiest way to expose
a module's functionalities to Rhai.
```rust ```rust
use rhai::{Engine, Module}; use rhai::{Engine, Module};
@ -41,6 +42,44 @@ engine.eval::<i64>("inc(41)")? == 42; // no need to import module
``` ```
Make the `Module` a Global Module
------------------------------------
`Engine::register_module` loads a [module] and makes it available globally under a specific namespace.
```rust
use rhai::{Engine, Module};
let mut module = Module::new(); // new module
module.set_fn_1("inc", |x: i64| Ok(x+1)); // use the 'set_fn_XXX' API to add functions
// Load the module into the Engine as a sub-module named 'calc'
let mut engine = Engine::new();
engine.register_module("calc", module);
engine.eval::<i64>("calc::inc(41)")? == 42; // refer to the 'Calc' module
```
`Engine::register_module` also exposes all _methods_ and _iterators_ from the module to the
_global_ namespace, so [getters/setters] and [indexers] for [custom types] work as expected.
```rust
use rhai::{Engine, Module};
let mut module = Module::new(); // new module
module.set_fn_1_mut("inc", // add new method
|x: &mut i64| Ok(x+1)
);
// Load the module into the Engine as a sub-module named 'calc'
let mut engine = Engine::new();
engine.register_module("calc", module);
// The method 'inc' works as expected because it is exposed to the global namespace
engine.eval::<i64>("let x = 41; x.inc()")? == 42;
```
Make the `Module` Dynamically Loadable Make the `Module` Dynamically Loadable
------------------------------------- -------------------------------------

View File

@ -10,7 +10,7 @@ packages to be used.
Packages typically contain Rust functions that are callable within a Rhai script. Packages typically contain Rust functions that are callable within a Rhai script.
All functions registered in a package is loaded under the _global namespace_ All functions registered in a package is loaded under the _global namespace_
(i.e. they're available without module qualifiers). (i.e. they're available without namespace qualifiers).
Once a package is created (e.g. via `Package::new`), it can be _shared_ (via `Package::get`) Once a package is created (e.g. via `Package::new`), it can be _shared_ (via `Package::get`)
among multiple instances of [`Engine`], even across threads (under [`sync`]). among multiple instances of [`Engine`], even across threads (under [`sync`]).

View File

@ -45,7 +45,7 @@ engine.register_raw_fn(
*x += y; // perform the action *x += y; // perform the action
Ok(().into()) // must be 'Result<Dynamic, Box<EvalAltResult>>' Ok(Dynamic::UNIT) // must be 'Result<Dynamic, Box<EvalAltResult>>'
} }
); );

View File

@ -37,7 +37,7 @@ wrapping this value.
The termination token is commonly used to provide information on the _reason_ or _source_ The termination token is commonly used to provide information on the _reason_ or _source_
behind the termination decision. behind the termination decision.
If the termination token is not needed, simply return `Some(().into())` to terminate the script If the termination token is not needed, simply return `Some(Dynamic::UNIT)` to terminate the script
run with [`()`] as the token. run with [`()`] as the token.

View File

@ -13,16 +13,12 @@ use crate::INT;
use crate::FLOAT; use crate::FLOAT;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::engine::Array; use crate::Array;
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::engine::Map; use crate::Map;
#[cfg(not(feature = "no_module"))]
use crate::engine::Imports;
use crate::stdlib::{ use crate::stdlib::{
any::TypeId,
borrow::Cow, borrow::Cow,
boxed::Box, boxed::Box,
collections::HashMap, collections::HashMap,
@ -38,6 +34,9 @@ use crate::stdlib::{
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
use crate::stdlib::collections::HashSet; use crate::stdlib::collections::HashSet;
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
use crate::stdlib::cmp::max;
/// A type representing the access mode of a scripted function. /// A type representing the access mode of a scripted function.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum FnAccess { pub enum FnAccess {
@ -90,7 +89,7 @@ pub struct ScriptFnDef {
pub lib: Option<Shared<Module>>, pub lib: Option<Shared<Module>>,
/// Encapsulated imported modules. /// Encapsulated imported modules.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub mods: Imports, pub mods: crate::engine::Imports,
/// Function name. /// Function name.
pub name: ImmutableString, pub name: ImmutableString,
/// Function access mode. /// Function access mode.
@ -127,7 +126,7 @@ impl fmt::Display for ScriptFnDef {
/// # Thread Safety /// # Thread Safety
/// ///
/// Currently, `AST` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. /// Currently, `AST` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone)]
pub struct AST( pub struct AST(
/// Global statements. /// Global statements.
Vec<Stmt>, Vec<Stmt>,
@ -135,20 +134,24 @@ pub struct AST(
Module, Module,
); );
impl Default for AST {
fn default() -> Self {
Self(Vec::with_capacity(16), Default::default())
}
}
impl AST { impl AST {
/// Create a new `AST`. /// Create a new `AST`.
#[inline(always)] #[inline(always)]
pub fn new(statements: Vec<Stmt>, lib: Module) -> Self { pub fn new(statements: impl IntoIterator<Item = Stmt>, lib: Module) -> Self {
Self(statements, lib) Self(statements.into_iter().collect(), lib)
} }
/// Get the statements. /// Get the statements.
#[cfg(not(feature = "internals"))] #[cfg(not(feature = "internals"))]
#[inline(always)] #[inline(always)]
pub(crate) fn statements(&self) -> &[Stmt] { pub(crate) fn statements(&self) -> &[Stmt] {
&self.0 &self.0
} }
/// _[INTERNALS]_ Get the statements. /// _[INTERNALS]_ Get the statements.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
@ -157,21 +160,18 @@ impl AST {
pub fn statements(&self) -> &[Stmt] { pub fn statements(&self) -> &[Stmt] {
&self.0 &self.0
} }
/// Get a mutable reference to the statements. /// Get a mutable reference to the statements.
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
#[inline(always)] #[inline(always)]
pub(crate) fn statements_mut(&mut self) -> &mut Vec<Stmt> { pub(crate) fn statements_mut(&mut self) -> &mut Vec<Stmt> {
&mut self.0 &mut self.0
} }
/// Get the internal `Module` containing all script-defined functions. /// Get the internal `Module` containing all script-defined functions.
#[cfg(not(feature = "internals"))] #[cfg(not(feature = "internals"))]
#[inline(always)] #[inline(always)]
pub(crate) fn lib(&self) -> &Module { pub(crate) fn lib(&self) -> &Module {
&self.1 &self.1
} }
/// _[INTERNALS]_ Get the internal `Module` containing all script-defined functions. /// _[INTERNALS]_ Get the internal `Module` containing all script-defined functions.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
@ -180,7 +180,6 @@ impl AST {
pub fn lib(&self) -> &Module { pub fn lib(&self) -> &Module {
&self.1 &self.1
} }
/// Clone the `AST`'s functions into a new `AST`. /// Clone the `AST`'s functions into a new `AST`.
/// No statements are cloned. /// No statements are cloned.
/// ///
@ -190,7 +189,6 @@ impl AST {
pub fn clone_functions_only(&self) -> Self { pub fn clone_functions_only(&self) -> Self {
self.clone_functions_only_filtered(|_, _, _| true) self.clone_functions_only_filtered(|_, _, _| true)
} }
/// Clone the `AST`'s functions into a new `AST` based on a filter predicate. /// Clone the `AST`'s functions into a new `AST` based on a filter predicate.
/// No statements are cloned. /// No statements are cloned.
/// ///
@ -205,14 +203,12 @@ impl AST {
functions.merge_filtered(&self.1, &mut filter); functions.merge_filtered(&self.1, &mut filter);
Self(Default::default(), functions) Self(Default::default(), functions)
} }
/// Clone the `AST`'s script statements into a new `AST`. /// Clone the `AST`'s script statements into a new `AST`.
/// No functions are cloned. /// No functions are cloned.
#[inline(always)] #[inline(always)]
pub fn clone_statements_only(&self) -> Self { pub fn clone_statements_only(&self) -> Self {
Self(self.0.clone(), Default::default()) Self(self.0.clone(), Default::default())
} }
/// Merge two `AST` into one. Both `AST`'s are untouched and a new, merged, version /// Merge two `AST` into one. Both `AST`'s are untouched and a new, merged, version
/// is returned. /// is returned.
/// ///
@ -266,7 +262,6 @@ impl AST {
pub fn merge(&self, other: &Self) -> Self { pub fn merge(&self, other: &Self) -> Self {
self.merge_filtered(other, |_, _, _| true) self.merge_filtered(other, |_, _, _| true)
} }
/// Combine one `AST` with another. The second `AST` is consumed. /// Combine one `AST` with another. The second `AST` is consumed.
/// ///
/// Statements in the second `AST` are simply appended to the end of the first _without any processing_. /// Statements in the second `AST` are simply appended to the end of the first _without any processing_.
@ -319,7 +314,6 @@ impl AST {
pub fn combine(&mut self, other: Self) -> &mut Self { pub fn combine(&mut self, other: Self) -> &mut Self {
self.combine_filtered(other, |_, _, _| true) self.combine_filtered(other, |_, _, _| true)
} }
/// Merge two `AST` into one. Both `AST`'s are untouched and a new, merged, version /// Merge two `AST` into one. Both `AST`'s are untouched and a new, merged, version
/// is returned. /// is returned.
/// ///
@ -395,7 +389,6 @@ impl AST {
Self::new(ast, functions) Self::new(ast, functions)
} }
/// Combine one `AST` with another. The second `AST` is consumed. /// Combine one `AST` with another. The second `AST` is consumed.
/// ///
/// Statements in the second `AST` are simply appended to the end of the first _without any processing_. /// Statements in the second `AST` are simply appended to the end of the first _without any processing_.
@ -457,7 +450,6 @@ impl AST {
functions.merge_filtered(&other.1, &mut filter); functions.merge_filtered(&other.1, &mut filter);
self self
} }
/// Filter out the functions, retaining only some based on a filter predicate. /// Filter out the functions, retaining only some based on a filter predicate.
/// ///
/// # Example /// # Example
@ -486,7 +478,6 @@ impl AST {
pub fn retain_functions(&mut self, filter: impl FnMut(FnAccess, &str, usize) -> bool) { pub fn retain_functions(&mut self, filter: impl FnMut(FnAccess, &str, usize) -> bool) {
self.1.retain_functions(filter); self.1.retain_functions(filter);
} }
/// Iterate through all functions /// Iterate through all functions
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[inline(always)] #[inline(always)]
@ -495,14 +486,12 @@ impl AST {
) -> impl Iterator<Item = (FnAccess, &str, usize, Shared<ScriptFnDef>)> + 'a { ) -> impl Iterator<Item = (FnAccess, &str, usize, Shared<ScriptFnDef>)> + 'a {
self.1.iter_script_fn() self.1.iter_script_fn()
} }
/// Clear all function definitions in the `AST`. /// Clear all function definitions in the `AST`.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[inline(always)] #[inline(always)]
pub fn clear_functions(&mut self) { pub fn clear_functions(&mut self) {
self.1 = Default::default(); self.1 = Default::default();
} }
/// Clear all statements in the `AST`, leaving only function definitions. /// Clear all statements in the `AST`, leaving only function definitions.
#[inline(always)] #[inline(always)]
pub fn clear_statements(&mut self) { pub fn clear_statements(&mut self) {
@ -605,7 +594,13 @@ pub enum Stmt {
/// No-op. /// No-op.
Noop(Position), Noop(Position),
/// if expr { stmt } else { stmt } /// if expr { stmt } else { stmt }
IfThenElse(Expr, Box<(Stmt, Option<Stmt>)>, Position), If(Expr, Box<(Stmt, Option<Stmt>)>, Position),
/// switch expr { literal or _ => stmt, ... }
Switch(
Expr,
Box<(HashMap<u64, Stmt, StraightHasherBuilder>, Option<Stmt>)>,
Position,
),
/// while expr { stmt } /// while expr { stmt }
While(Expr, Box<Stmt>, Position), While(Expr, Box<Stmt>, Position),
/// loop { stmt } /// loop { stmt }
@ -629,7 +624,7 @@ pub enum Stmt {
/// break /// break
Break(Position), Break(Position),
/// return/throw /// return/throw
ReturnWithVal((ReturnType, Position), Option<Expr>, Position), Return((ReturnType, Position), Option<Expr>, Position),
/// import expr as var /// import expr as var
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Import(Expr, Option<Box<IdentX>>, Position), Import(Expr, Option<Box<IdentX>>, Position),
@ -656,7 +651,6 @@ impl Stmt {
_ => false, _ => false,
} }
} }
/// Get the `Position` of this statement. /// Get the `Position` of this statement.
pub fn position(&self) -> Position { pub fn position(&self) -> Position {
match self { match self {
@ -665,11 +659,12 @@ impl Stmt {
| Self::Break(pos) | Self::Break(pos)
| Self::Block(_, pos) | Self::Block(_, pos)
| Self::Assignment(_, pos) | Self::Assignment(_, pos)
| Self::IfThenElse(_, _, pos) | Self::If(_, _, pos)
| Self::Switch(_, _, pos)
| Self::While(_, _, pos) | Self::While(_, _, pos)
| Self::Loop(_, pos) | Self::Loop(_, pos)
| Self::For(_, _, pos) | Self::For(_, _, pos)
| Self::ReturnWithVal((_, pos), _, _) | Self::Return((_, pos), _, _)
| Self::Let(_, _, _, pos) | Self::Let(_, _, _, pos)
| Self::Const(_, _, _, pos) | Self::Const(_, _, _, pos)
| Self::TryCatch(_, pos, _) => *pos, | Self::TryCatch(_, pos, _) => *pos,
@ -685,7 +680,6 @@ impl Stmt {
Self::Share(x) => x.pos, Self::Share(x) => x.pos,
} }
} }
/// Override the `Position` of this statement. /// Override the `Position` of this statement.
pub fn set_position(&mut self, new_pos: Position) -> &mut Self { pub fn set_position(&mut self, new_pos: Position) -> &mut Self {
match self { match self {
@ -694,11 +688,12 @@ impl Stmt {
| Self::Break(pos) | Self::Break(pos)
| Self::Block(_, pos) | Self::Block(_, pos)
| Self::Assignment(_, pos) | Self::Assignment(_, pos)
| Self::IfThenElse(_, _, pos) | Self::If(_, _, pos)
| Self::Switch(_, _, pos)
| Self::While(_, _, pos) | Self::While(_, _, pos)
| Self::Loop(_, pos) | Self::Loop(_, pos)
| Self::For(_, _, pos) | Self::For(_, _, pos)
| Self::ReturnWithVal((_, pos), _, _) | Self::Return((_, pos), _, _)
| Self::Let(_, _, _, pos) | Self::Let(_, _, _, pos)
| Self::Const(_, _, _, pos) | Self::Const(_, _, _, pos)
| Self::TryCatch(_, pos, _) => *pos = new_pos, | Self::TryCatch(_, pos, _) => *pos = new_pos,
@ -718,11 +713,11 @@ impl Stmt {
self self
} }
/// Is this statement self-terminated (i.e. no need for a semicolon terminator)? /// Is this statement self-terminated (i.e. no need for a semicolon terminator)?
pub fn is_self_terminated(&self) -> bool { pub fn is_self_terminated(&self) -> bool {
match self { match self {
Self::IfThenElse(_, _, _) Self::If(_, _, _)
| Self::Switch(_, _, _)
| Self::While(_, _, _) | Self::While(_, _, _)
| Self::Loop(_, _) | Self::Loop(_, _)
| Self::For(_, _, _) | Self::For(_, _, _)
@ -738,7 +733,7 @@ impl Stmt {
| Self::Expr(_) | Self::Expr(_)
| Self::Continue(_) | Self::Continue(_)
| Self::Break(_) | Self::Break(_)
| Self::ReturnWithVal(_, _, _) => false, | Self::Return(_, _, _) => false,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Self::Import(_, _, _) | Self::Export(_, _) => false, Self::Import(_, _, _) | Self::Export(_, _) => false,
@ -747,22 +742,27 @@ impl Stmt {
Self::Share(_) => false, Self::Share(_) => false,
} }
} }
/// Is this statement _pure_? /// Is this statement _pure_?
pub fn is_pure(&self) -> bool { pub fn is_pure(&self) -> bool {
match self { match self {
Self::Noop(_) => true, Self::Noop(_) => true,
Self::Expr(expr) => expr.is_pure(), Self::Expr(expr) => expr.is_pure(),
Self::IfThenElse(condition, x, _) if x.1.is_some() => { Self::If(condition, x, _) => {
condition.is_pure() && x.0.is_pure() && x.1.as_ref().unwrap().is_pure() condition.is_pure()
&& x.0.is_pure()
&& x.1.as_ref().map(Stmt::is_pure).unwrap_or(true)
}
Self::Switch(expr, x, _) => {
expr.is_pure()
&& x.0.values().all(Stmt::is_pure)
&& x.1.as_ref().map(Stmt::is_pure).unwrap_or(true)
} }
Self::IfThenElse(condition, x, _) => condition.is_pure() && x.0.is_pure(),
Self::While(condition, block, _) => condition.is_pure() && block.is_pure(), Self::While(condition, block, _) => condition.is_pure() && block.is_pure(),
Self::Loop(block, _) => block.is_pure(), Self::Loop(block, _) => block.is_pure(),
Self::For(iterable, x, _) => iterable.is_pure() && x.1.is_pure(), Self::For(iterable, x, _) => iterable.is_pure() && x.1.is_pure(),
Self::Let(_, _, _, _) | Self::Const(_, _, _, _) | Self::Assignment(_, _) => false, Self::Let(_, _, _, _) | Self::Const(_, _, _, _) | Self::Assignment(_, _) => false,
Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()), Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()),
Self::Continue(_) | Self::Break(_) | Self::ReturnWithVal(_, _, _) => false, Self::Continue(_) | Self::Break(_) | Self::Return(_, _, _) => false,
Self::TryCatch(x, _, _) => x.0.is_pure() && x.2.is_pure(), Self::TryCatch(x, _, _) => x.0.is_pure() && x.2.is_pure(),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -860,6 +860,10 @@ pub struct FnCallExpr {
/// This type is volatile and may change. /// This type is volatile and may change.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Expr { pub enum Expr {
/// Dynamic constant.
/// Used to hold either an Array or Map literal for quick cloning.
/// All other primitive data types should use the appropriate variants for better speed.
DynamicConstant(Box<Dynamic>, Position),
/// Integer constant. /// Integer constant.
IntegerConstant(INT, Position), IntegerConstant(INT, Position),
/// Floating-point constant. /// Floating-point constant.
@ -895,15 +899,6 @@ pub enum Expr {
Dot(Box<BinaryExpr>, Position), Dot(Box<BinaryExpr>, Position),
/// expr[expr] /// expr[expr]
Index(Box<BinaryExpr>, Position), Index(Box<BinaryExpr>, Position),
/// switch expr { literal or _ => stmt, ... }
Switch(
Box<(
Expr,
HashMap<u64, Stmt, StraightHasherBuilder>,
Option<Stmt>,
)>,
Position,
),
/// lhs in rhs /// lhs in rhs
In(Box<BinaryExpr>, Position), In(Box<BinaryExpr>, Position),
/// lhs && rhs /// lhs && rhs
@ -922,34 +917,6 @@ impl Default for Expr {
} }
impl Expr { impl Expr {
/// Get the type of an expression.
///
/// Returns `None` if the expression's result type is not constant.
pub fn get_type_id(&self) -> Option<TypeId> {
Some(match self {
Self::Expr(x) => return x.get_type_id(),
Self::IntegerConstant(_, _) => TypeId::of::<INT>(),
#[cfg(not(feature = "no_float"))]
Self::FloatConstant(_, _) => TypeId::of::<FLOAT>(),
Self::CharConstant(_, _) => TypeId::of::<char>(),
Self::StringConstant(_, _) => TypeId::of::<ImmutableString>(),
Self::FnPointer(_, _) => TypeId::of::<FnPtr>(),
Self::True(_) | Self::False(_) | Self::In(_, _) | Self::And(_, _) | Self::Or(_, _) => {
TypeId::of::<bool>()
}
Self::Unit(_) => TypeId::of::<()>(),
#[cfg(not(feature = "no_index"))]
Self::Array(_, _) => TypeId::of::<Array>(),
#[cfg(not(feature = "no_object"))]
Self::Map(_, _) => TypeId::of::<Map>(),
_ => return None,
})
}
/// Get the `Dynamic` value of a constant expression. /// Get the `Dynamic` value of a constant expression.
/// ///
/// Returns `None` if the expression is not constant. /// Returns `None` if the expression is not constant.
@ -957,6 +924,7 @@ impl Expr {
Some(match self { Some(match self {
Self::Expr(x) => return x.get_constant_value(), Self::Expr(x) => return x.get_constant_value(),
Self::DynamicConstant(x, _) => x.as_ref().clone(),
Self::IntegerConstant(x, _) => (*x).into(), Self::IntegerConstant(x, _) => (*x).into(),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Self::FloatConstant(x, _) => (*x).into(), Self::FloatConstant(x, _) => (*x).into(),
@ -971,23 +939,25 @@ impl Expr {
Self::Unit(_) => ().into(), Self::Unit(_) => ().into(),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Self::Array(x, _) if x.iter().all(Self::is_constant) => Dynamic(Union::Array( Self::Array(x, _) if self.is_constant() => {
Box::new(x.iter().map(|v| v.get_constant_value().unwrap()).collect()), let mut arr = Array::with_capacity(max(crate::engine::TYPICAL_ARRAY_SIZE, x.len()));
)), arr.extend(x.iter().map(|v| v.get_constant_value().unwrap()));
Dynamic(Union::Array(Box::new(arr)))
}
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Self::Map(x, _) if x.iter().all(|(_, v)| v.is_constant()) => { Self::Map(x, _) if self.is_constant() => {
Dynamic(Union::Map(Box::new( let mut map = Map::with_capacity(max(crate::engine::TYPICAL_MAP_SIZE, x.len()));
map.extend(
x.iter() x.iter()
.map(|(k, v)| (k.name.clone(), v.get_constant_value().unwrap())) .map(|(k, v)| (k.name.clone(), v.get_constant_value().unwrap())),
.collect(), );
))) Dynamic(Union::Map(Box::new(map)))
} }
_ => return None, _ => return None,
}) })
} }
/// Is the expression a simple variable access? /// Is the expression a simple variable access?
pub(crate) fn get_variable_access(&self, non_qualified: bool) -> Option<&str> { pub(crate) fn get_variable_access(&self, non_qualified: bool) -> Option<&str> {
match self { match self {
@ -995,7 +965,6 @@ impl Expr {
_ => None, _ => None,
} }
} }
/// Get the `Position` of the expression. /// Get the `Position` of the expression.
pub fn position(&self) -> Position { pub fn position(&self) -> Position {
match self { match self {
@ -1004,6 +973,7 @@ impl Expr {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Self::FloatConstant(_, pos) => *pos, Self::FloatConstant(_, pos) => *pos,
Self::DynamicConstant(_, pos) => *pos,
Self::IntegerConstant(_, pos) => *pos, Self::IntegerConstant(_, pos) => *pos,
Self::CharConstant(_, pos) => *pos, Self::CharConstant(_, pos) => *pos,
Self::StringConstant(_, pos) => *pos, Self::StringConstant(_, pos) => *pos,
@ -1014,7 +984,6 @@ impl Expr {
Self::Stmt(_, pos) => *pos, Self::Stmt(_, pos) => *pos,
Self::Variable(x) => (x.3).pos, Self::Variable(x) => (x.3).pos,
Self::FnCall(_, pos) => *pos, Self::FnCall(_, pos) => *pos,
Self::Switch(_, pos) => *pos,
Self::And(x, _) | Self::Or(x, _) | Self::In(x, _) => x.lhs.position(), Self::And(x, _) | Self::Or(x, _) | Self::In(x, _) => x.lhs.position(),
@ -1025,7 +994,6 @@ impl Expr {
Self::Custom(_, pos) => *pos, Self::Custom(_, pos) => *pos,
} }
} }
/// Override the `Position` of the expression. /// Override the `Position` of the expression.
pub fn set_position(&mut self, new_pos: Position) -> &mut Self { pub fn set_position(&mut self, new_pos: Position) -> &mut Self {
match self { match self {
@ -1036,6 +1004,7 @@ impl Expr {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Self::FloatConstant(_, pos) => *pos = new_pos, Self::FloatConstant(_, pos) => *pos = new_pos,
Self::DynamicConstant(_, pos) => *pos = new_pos,
Self::IntegerConstant(_, pos) => *pos = new_pos, Self::IntegerConstant(_, pos) => *pos = new_pos,
Self::CharConstant(_, pos) => *pos = new_pos, Self::CharConstant(_, pos) => *pos = new_pos,
Self::StringConstant(_, pos) => *pos = new_pos, Self::StringConstant(_, pos) => *pos = new_pos,
@ -1046,7 +1015,6 @@ impl Expr {
Self::Property(x) => (x.1).pos = new_pos, Self::Property(x) => (x.1).pos = new_pos,
Self::Stmt(_, pos) => *pos = new_pos, Self::Stmt(_, pos) => *pos = new_pos,
Self::FnCall(_, pos) => *pos = new_pos, Self::FnCall(_, pos) => *pos = new_pos,
Self::Switch(_, pos) => *pos = new_pos,
Self::And(_, pos) | Self::Or(_, pos) | Self::In(_, pos) => *pos = new_pos, Self::And(_, pos) | Self::Or(_, pos) | Self::In(_, pos) => *pos = new_pos,
Self::True(pos) | Self::False(pos) | Self::Unit(pos) => *pos = new_pos, Self::True(pos) | Self::False(pos) | Self::Unit(pos) => *pos = new_pos,
Self::Dot(_, pos) | Self::Index(_, pos) => *pos = new_pos, Self::Dot(_, pos) | Self::Index(_, pos) => *pos = new_pos,
@ -1055,7 +1023,6 @@ impl Expr {
self self
} }
/// Is the expression pure? /// Is the expression pure?
/// ///
/// A pure expression has no side effects. /// A pure expression has no side effects.
@ -1078,7 +1045,6 @@ impl Expr {
_ => self.is_constant(), _ => self.is_constant(),
} }
} }
/// Is the expression the unit `()` literal? /// Is the expression the unit `()` literal?
#[inline(always)] #[inline(always)]
pub fn is_unit(&self) -> bool { pub fn is_unit(&self) -> bool {
@ -1087,7 +1053,6 @@ impl Expr {
_ => false, _ => false,
} }
} }
/// Is the expression a constant? /// Is the expression a constant?
pub fn is_constant(&self) -> bool { pub fn is_constant(&self) -> bool {
match self { match self {
@ -1096,7 +1061,8 @@ impl Expr {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Self::FloatConstant(_, _) => true, Self::FloatConstant(_, _) => true,
Self::IntegerConstant(_, _) Self::DynamicConstant(_, _)
| Self::IntegerConstant(_, _)
| Self::CharConstant(_, _) | Self::CharConstant(_, _)
| Self::StringConstant(_, _) | Self::StringConstant(_, _)
| Self::FnPointer(_, _) | Self::FnPointer(_, _)
@ -1120,7 +1086,6 @@ impl Expr {
_ => false, _ => false,
} }
} }
/// Is a particular token allowed as a postfix operator to this expression? /// Is a particular token allowed as a postfix operator to this expression?
pub fn is_valid_postfix(&self, token: &Token) -> bool { pub fn is_valid_postfix(&self, token: &Token) -> bool {
match self { match self {
@ -1129,7 +1094,8 @@ impl Expr {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Self::FloatConstant(_, _) => false, Self::FloatConstant(_, _) => false,
Self::IntegerConstant(_, _) Self::DynamicConstant(_, _)
| Self::IntegerConstant(_, _)
| Self::CharConstant(_, _) | Self::CharConstant(_, _)
| Self::FnPointer(_, _) | Self::FnPointer(_, _)
| Self::In(_, _) | Self::In(_, _)
@ -1142,7 +1108,6 @@ impl Expr {
Self::StringConstant(_, _) Self::StringConstant(_, _)
| Self::Stmt(_, _) | Self::Stmt(_, _)
| Self::FnCall(_, _) | Self::FnCall(_, _)
| Self::Switch(_, _)
| Self::Dot(_, _) | Self::Dot(_, _)
| Self::Index(_, _) | Self::Index(_, _)
| Self::Array(_, _) | Self::Array(_, _)

View File

@ -3,43 +3,27 @@
use crate::fn_native::{FnPtr, SendSync}; use crate::fn_native::{FnPtr, SendSync};
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast}; use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
use crate::utils::ImmutableString; use crate::utils::ImmutableString;
use crate::{StaticVec, INT}; use crate::INT;
#[cfg(not(feature = "no_closure"))]
use crate::fn_native::{shared_try_take, Locked, Shared};
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::FLOAT; use crate::FLOAT;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::engine::Array; use crate::Array;
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::engine::Map; use crate::Map;
use crate::stdlib::{ use crate::stdlib::{
any::{type_name, Any, TypeId}, any::{type_name, Any, TypeId},
boxed::Box, boxed::Box,
fmt, fmt,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
mem,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
string::{String, ToString}, string::{String, ToString},
}; };
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))]
use crate::stdlib::cell::{Ref, RefMut};
#[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")]
use crate::stdlib::sync::{RwLockReadGuard, RwLockWriteGuard};
#[cfg(not(feature = "no_object"))]
use crate::stdlib::collections::HashMap;
#[cfg(not(feature = "no_index"))]
use crate::stdlib::vec::Vec;
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use crate::stdlib::time::Instant; use crate::stdlib::time::Instant;
@ -164,7 +148,7 @@ pub enum Union {
Variant(Box<Box<dyn Variant>>), Variant(Box<Box<dyn Variant>>),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Shared(Shared<Locked<Dynamic>>), Shared(crate::Shared<crate::Locked<Dynamic>>),
} }
/// Underlying `Variant` read guard for `Dynamic`. /// Underlying `Variant` read guard for `Dynamic`.
@ -183,11 +167,11 @@ enum DynamicReadLockInner<'d, T: Variant + Clone> {
/// A read guard to a shared `RefCell`. /// A read guard to a shared `RefCell`.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
Guard(Ref<'d, Dynamic>), Guard(crate::stdlib::cell::Ref<'d, Dynamic>),
/// A read guard to a shared `RwLock`. /// A read guard to a shared `RwLock`.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
Guard(RwLockReadGuard<'d, Dynamic>), Guard(crate::stdlib::sync::RwLockReadGuard<'d, Dynamic>),
} }
impl<'d, T: Variant + Clone> Deref for DynamicReadLock<'d, T> { impl<'d, T: Variant + Clone> Deref for DynamicReadLock<'d, T> {
@ -220,11 +204,11 @@ enum DynamicWriteLockInner<'d, T: Variant + Clone> {
/// A write guard to a shared `RefCell`. /// A write guard to a shared `RefCell`.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
Guard(RefMut<'d, Dynamic>), Guard(crate::stdlib::cell::RefMut<'d, Dynamic>),
/// A write guard to a shared `RwLock`. /// A write guard to a shared `RwLock`.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
Guard(RwLockWriteGuard<'d, Dynamic>), Guard(crate::stdlib::sync::RwLockWriteGuard<'d, Dynamic>),
} }
impl<'d, T: Variant + Clone> Deref for DynamicWriteLock<'d, T> { impl<'d, T: Variant + Clone> Deref for DynamicWriteLock<'d, T> {
@ -263,7 +247,6 @@ impl Dynamic {
_ => false, _ => false,
} }
} }
/// Does this `Dynamic` hold a shared data type /// Does this `Dynamic` hold a shared data type
/// instead of one of the supported system primitive types? /// instead of one of the supported system primitive types?
#[inline(always)] #[inline(always)]
@ -274,7 +257,6 @@ impl Dynamic {
_ => false, _ => false,
} }
} }
/// Is the value held by this `Dynamic` a particular type? /// Is the value held by this `Dynamic` a particular type?
/// ///
/// If the `Dynamic` is a Shared variant checking is performed on /// If the `Dynamic` is a Shared variant checking is performed on
@ -289,7 +271,6 @@ impl Dynamic {
self.type_id() == target_type_id self.type_id() == target_type_id
} }
/// Get the TypeId of the value held by this `Dynamic`. /// Get the TypeId of the value held by this `Dynamic`.
/// ///
/// # Panics or Deadlocks When Value is Shared /// # Panics or Deadlocks When Value is Shared
@ -323,7 +304,6 @@ impl Dynamic {
Union::Shared(cell) => (*cell.read().unwrap()).type_id(), Union::Shared(cell) => (*cell.read().unwrap()).type_id(),
} }
} }
/// Get the name of the type of the value held by this `Dynamic`. /// Get the name of the type of the value held by this `Dynamic`.
/// ///
/// # Panics or Deadlocks When Value is Shared /// # Panics or Deadlocks When Value is Shared
@ -364,6 +344,8 @@ impl Dynamic {
impl Hash for Dynamic { impl Hash for Dynamic {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
mem::discriminant(self).hash(state);
match &self.0 { match &self.0 {
Union::Unit(_) => ().hash(state), Union::Unit(_) => ().hash(state),
Union::Bool(value) => value.hash(state), Union::Bool(value) => value.hash(state),
@ -371,20 +353,17 @@ impl Hash for Dynamic {
Union::Char(ch) => ch.hash(state), Union::Char(ch) => ch.hash(state),
Union::Int(i) => i.hash(state), Union::Int(i) => i.hash(state),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(f) => { Union::Float(f) => f.to_le_bytes().hash(state),
TypeId::of::<FLOAT>().hash(state);
state.write(&f.to_le_bytes());
}
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(a) => a.hash(state), Union::Array(a) => (**a).hash(state),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(m) => { Union::Map(m) => {
let mut buf: StaticVec<_> = m.keys().collect(); let mut buf: crate::StaticVec<_> = m.iter().collect();
buf.sort(); buf.sort_by(|(a, _), (b, _)| a.cmp(b));
buf.into_iter().for_each(|key| { buf.into_iter().for_each(|(key, value)| {
key.hash(state); key.hash(state);
m[key].hash(state); value.hash(state);
}) })
} }
@ -536,11 +515,33 @@ impl Clone for Dynamic {
impl Default for Dynamic { impl Default for Dynamic {
#[inline(always)] #[inline(always)]
fn default() -> Self { fn default() -> Self {
Self(Union::Unit(())) Self::UNIT
} }
} }
impl Dynamic { impl Dynamic {
/// A `Dynamic` containing a `()`.
pub const UNIT: Dynamic = Self(Union::Unit(()));
/// A `Dynamic` containing a `true`.
pub const TRUE: Dynamic = Self(Union::Bool(true));
/// A `Dynamic` containing a `false`.
pub const FALSE: Dynamic = Self(Union::Bool(false));
/// A `Dynamic` containing the integer zero.
pub const ZERO: Dynamic = Self(Union::Int(0));
/// A `Dynamic` containing the integer one.
pub const ONE: Dynamic = Self(Union::Int(1));
/// A `Dynamic` containing the integer negative one.
pub const NEGATIVE_ONE: Dynamic = Self(Union::Int(-1));
/// A `Dynamic` containing the floating-point zero.
#[cfg(not(feature = "no_float"))]
pub const FLOAT_ZERO: Dynamic = Self(Union::Float(0.0));
/// A `Dynamic` containing the floating-point one.
#[cfg(not(feature = "no_float"))]
pub const FLOAT_ONE: Dynamic = Self(Union::Float(1.0));
/// A `Dynamic` containing the floating-point negative one.
#[cfg(not(feature = "no_float"))]
pub const FLOAT_NEGATIVE_ONE: Dynamic = Self(Union::Float(-1.0));
/// Create a `Dynamic` from any type. A `Dynamic` value is simply returned as is. /// Create a `Dynamic` from any type. A `Dynamic` value is simply returned as is.
/// ///
/// # Safety /// # Safety
@ -676,7 +677,7 @@ impl Dynamic {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
return match self.0 { return match self.0 {
Union::Shared(..) => self, Union::Shared(..) => self,
_ => Self(Union::Shared(Locked::new(self).into())), _ => Self(Union::Shared(crate::Locked::new(self).into())),
}; };
#[cfg(feature = "no_closure")] #[cfg(feature = "no_closure")]
@ -884,7 +885,7 @@ impl Dynamic {
pub fn flatten(self) -> Self { pub fn flatten(self) -> Self {
match self.0 { match self.0 {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(cell) => shared_try_take(cell).map_or_else( Union::Shared(cell) => crate::fn_native::shared_try_take(cell).map_or_else(
|cell| { |cell| {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
return cell.borrow().clone(); return cell.borrow().clone();
@ -1294,9 +1295,9 @@ impl<S: Into<ImmutableString>> From<S> for Dynamic {
} }
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
impl<T: Variant + Clone> From<Vec<T>> for Dynamic { impl<T: Variant + Clone> From<crate::stdlib::vec::Vec<T>> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: Vec<T>) -> Self { fn from(value: crate::stdlib::vec::Vec<T>) -> Self {
Self(Union::Array(Box::new( Self(Union::Array(Box::new(
value.into_iter().map(Dynamic::from).collect(), value.into_iter().map(Dynamic::from).collect(),
))) )))
@ -1312,9 +1313,11 @@ impl<T: Variant + Clone> From<&[T]> for Dynamic {
} }
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
impl<K: Into<ImmutableString>, T: Variant + Clone> From<HashMap<K, T>> for Dynamic { impl<K: Into<ImmutableString>, T: Variant + Clone> From<crate::stdlib::collections::HashMap<K, T>>
for Dynamic
{
#[inline(always)] #[inline(always)]
fn from(value: HashMap<K, T>) -> Self { fn from(value: crate::stdlib::collections::HashMap<K, T>) -> Self {
Self(Union::Map(Box::new( Self(Union::Map(Box::new(
value value
.into_iter() .into_iter()

View File

@ -1,9 +1,9 @@
//! Main module defining the script evaluation `Engine`. //! Main module defining the script evaluation `Engine`.
use crate::ast::{BinaryExpr, Expr, FnCallExpr, Ident, IdentX, ReturnType, Stmt}; use crate::ast::{Expr, FnCallExpr, Ident, IdentX, ReturnType, Stmt};
use crate::dynamic::{map_std_type_name, Dynamic, Union, Variant}; use crate::dynamic::{map_std_type_name, Dynamic, Union, Variant};
use crate::fn_call::run_builtin_op_assignment; use crate::fn_call::run_builtin_op_assignment;
use crate::fn_native::{Callback, FnPtr, OnVarCallback, Shared}; use crate::fn_native::{CallableFunction, Callback, FnPtr, IteratorFn, OnVarCallback, Shared};
use crate::module::{Module, NamespaceRef}; use crate::module::{Module, NamespaceRef};
use crate::optimize::OptimizationLevel; use crate::optimize::OptimizationLevel;
use crate::packages::{Package, PackagesCollection, StandardPackage}; use crate::packages::{Package, PackagesCollection, StandardPackage};
@ -12,29 +12,11 @@ use crate::result::EvalAltResult;
use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::scope::{EntryType as ScopeEntryType, Scope};
use crate::syntax::CustomSyntax; use crate::syntax::CustomSyntax;
use crate::token::{Position, NO_POS}; use crate::token::{Position, NO_POS};
use crate::utils::get_hasher; use crate::utils::{get_hasher, ImmutableString};
use crate::{calc_native_fn_hash, StaticVec}; use crate::{calc_native_fn_hash, StaticVec};
#[cfg(not(feature = "no_index"))]
use crate::INT;
#[cfg(not(feature = "no_module"))]
use crate::{fn_native::shared_take_or_clone, module::ModuleResolver};
#[cfg(not(feature = "no_std"))]
#[cfg(not(feature = "no_module"))]
#[cfg(not(target_arch = "wasm32"))]
use crate::module::resolvers;
#[cfg(any(not(feature = "no_object"), not(feature = "no_module")))]
use crate::utils::ImmutableString;
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
use crate::dynamic::DynamicWriteLock;
use crate::stdlib::{ use crate::stdlib::{
any::type_name, any::{type_name, TypeId},
borrow::Cow, borrow::Cow,
boxed::Box, boxed::Box,
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
@ -44,20 +26,16 @@ use crate::stdlib::{
num::NonZeroUsize, num::NonZeroUsize,
ops::DerefMut, ops::DerefMut,
string::{String, ToString}, string::{String, ToString},
vec::Vec,
}; };
#[cfg(not(feature = "no_index"))]
use crate::stdlib::any::TypeId;
#[cfg(not(feature = "no_closure"))]
use crate::stdlib::mem;
/// Variable-sized array of `Dynamic` values. /// Variable-sized array of `Dynamic` values.
/// ///
/// Not available under the `no_index` feature. /// Not available under the `no_index` feature.
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub type Array = Vec<Dynamic>; pub type Array = crate::stdlib::vec::Vec<Dynamic>;
#[cfg(not(feature = "no_index"))]
pub const TYPICAL_ARRAY_SIZE: usize = 8; // Small arrays are typical
/// Hash map of `Dynamic` values with `ImmutableString` keys. /// Hash map of `Dynamic` values with `ImmutableString` keys.
/// ///
@ -65,6 +43,9 @@ pub type Array = Vec<Dynamic>;
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub type Map = HashMap<ImmutableString, Dynamic>; pub type Map = HashMap<ImmutableString, Dynamic>;
#[cfg(not(feature = "no_object"))]
pub const TYPICAL_MAP_SIZE: usize = 8; // Small maps are typical
/// _[INTERNALS]_ A stack of imported modules. /// _[INTERNALS]_ A stack of imported modules.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
/// ///
@ -77,7 +58,7 @@ pub type Map = HashMap<ImmutableString, Dynamic>;
// We cannot use &str or Cow<str> here because `eval` may load a module and the module name will live beyond // We cannot use &str or Cow<str> here because `eval` may load a module and the module name will live beyond
// the AST of the eval script text. The best we can do is a shared reference. // the AST of the eval script text. The best we can do is a shared reference.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct Imports(StaticVec<(ImmutableString, Shared<Module>)>); pub struct Imports(StaticVec<(ImmutableString, bool, Shared<Module>)>);
impl Imports { impl Imports {
/// Get the length of this stack of imported modules. /// Get the length of this stack of imported modules.
@ -90,7 +71,7 @@ impl Imports {
} }
/// Get the imported module at a particular index. /// Get the imported module at a particular index.
pub fn get(&self, index: usize) -> Option<Shared<Module>> { pub fn get(&self, index: usize) -> Option<Shared<Module>> {
self.0.get(index).map(|(_, m)| m).cloned() self.0.get(index).map(|(_, _, m)| m).cloned()
} }
/// Get the index of an imported module by name. /// Get the index of an imported module by name.
pub fn find(&self, name: &str) -> Option<usize> { pub fn find(&self, name: &str) -> Option<usize> {
@ -98,12 +79,21 @@ impl Imports {
.iter() .iter()
.enumerate() .enumerate()
.rev() .rev()
.find(|(_, (key, _))| key.as_str() == name) .find(|(_, (key, _, _))| key.as_str() == name)
.map(|(index, _)| index) .map(|(index, _)| index)
} }
/// Push an imported module onto the stack. /// Push an imported module onto the stack.
pub fn push(&mut self, name: impl Into<ImmutableString>, module: impl Into<Shared<Module>>) { pub fn push(&mut self, name: impl Into<ImmutableString>, module: impl Into<Shared<Module>>) {
self.0.push((name.into(), module.into())); self.0.push((name.into(), false, module.into()));
}
/// Push a fixed module onto the stack.
#[cfg(not(feature = "no_module"))]
pub(crate) fn push_fixed(
&mut self,
name: impl Into<ImmutableString>,
module: impl Into<Shared<Module>>,
) {
self.0.push((name.into(), true, module.into()));
} }
/// Truncate the stack of imported modules to a particular length. /// Truncate the stack of imported modules to a particular length.
pub fn truncate(&mut self, size: usize) { pub fn truncate(&mut self, size: usize) {
@ -111,26 +101,59 @@ impl Imports {
} }
/// Get an iterator to this stack of imported modules. /// Get an iterator to this stack of imported modules.
#[allow(dead_code)] #[allow(dead_code)]
pub fn iter(&self) -> impl Iterator<Item = (&str, Shared<Module>)> { pub fn iter(&self) -> impl Iterator<Item = (&str, bool, Shared<Module>)> {
self.0 self.0
.iter() .iter()
.map(|(name, module)| (name.as_str(), module.clone())) .map(|(name, fixed, module)| (name.as_str(), *fixed, module.clone()))
} }
/// Get an iterator to this stack of imported modules. /// Get an iterator to this stack of imported modules.
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn iter_raw<'a>( pub(crate) fn iter_raw<'a>(
&'a self, &'a self,
) -> impl Iterator<Item = (ImmutableString, Shared<Module>)> + 'a { ) -> impl Iterator<Item = (ImmutableString, bool, Shared<Module>)> + 'a {
self.0.iter().cloned() self.0.iter().cloned()
} }
/// Get a consuming iterator to this stack of imported modules. /// Get a consuming iterator to this stack of imported modules.
pub fn into_iter(self) -> impl Iterator<Item = (ImmutableString, Shared<Module>)> { pub fn into_iter(self) -> impl Iterator<Item = (ImmutableString, bool, Shared<Module>)> {
self.0.into_iter() self.0.into_iter()
} }
/// Add a stream of imported modules. /// Add a stream of imported modules.
pub fn extend(&mut self, stream: impl Iterator<Item = (ImmutableString, Shared<Module>)>) { pub fn extend(
&mut self,
stream: impl Iterator<Item = (ImmutableString, bool, Shared<Module>)>,
) {
self.0.extend(stream) self.0.extend(stream)
} }
/// Does the specified function hash key exist in this stack of imported modules?
#[allow(dead_code)]
pub fn contains_fn(&self, hash: u64) -> bool {
self.0
.iter()
.any(|(_, fixed, m)| *fixed && m.contains_qualified_fn(hash))
}
/// Get specified function via its hash key.
pub fn get_fn(&self, hash: u64) -> Option<&CallableFunction> {
self.0
.iter()
.rev()
.filter(|&&(_, fixed, _)| fixed)
.find_map(|(_, _, m)| m.get_qualified_fn(hash))
}
/// Does the specified TypeId iterator exist in this stack of imported modules?
#[allow(dead_code)]
pub fn contains_iter(&self, id: TypeId) -> bool {
self.0
.iter()
.any(|(_, fixed, m)| *fixed && m.contains_qualified_iter(id))
}
/// Get the specified TypeId iterator.
pub fn get_iter(&self, id: TypeId) -> Option<IteratorFn> {
self.0
.iter()
.rev()
.filter(|&&(_, fixed, _)| fixed)
.find_map(|(_, _, m)| m.get_qualified_iter(id))
}
} }
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
@ -178,6 +201,7 @@ pub const FN_IDX_GET: &str = "index$get$";
pub const FN_IDX_SET: &str = "index$set$"; pub const FN_IDX_SET: &str = "index$set$";
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub const FN_ANONYMOUS: &str = "anon$"; pub const FN_ANONYMOUS: &str = "anon$";
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
pub const OP_EQUALS: &str = "=="; pub const OP_EQUALS: &str = "==";
pub const MARKER_EXPR: &str = "$expr$"; pub const MARKER_EXPR: &str = "$expr$";
pub const MARKER_BLOCK: &str = "$block$"; pub const MARKER_BLOCK: &str = "$block$";
@ -251,7 +275,7 @@ pub enum Target<'a> {
/// It holds both the access guard and the original shared value. /// It holds both the access guard and the original shared value.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
LockGuard((DynamicWriteLock<'a, Dynamic>, Dynamic)), LockGuard((crate::dynamic::DynamicWriteLock<'a, Dynamic>, Dynamic)),
/// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects). /// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects).
Value(Dynamic), Value(Dynamic),
/// The target is a character inside a String. /// The target is a character inside a String.
@ -338,18 +362,10 @@ impl<'a> Target<'a> {
_ => None, _ => None,
} }
} }
/// Get a mutable reference from the `Target`. /// Convert a shared or reference `Target` into a target with an owned value.
#[inline(always)] #[inline(always)]
pub fn as_mut(&mut self) -> &mut Dynamic { pub fn into_owned(self) -> Target<'static> {
match self { self.take_or_clone().into()
Self::Ref(r) => *r,
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
Self::LockGuard((r, _)) => r.deref_mut(),
Self::Value(ref mut r) => r,
#[cfg(not(feature = "no_index"))]
Self::StringChar(_, _, ref mut r) => r,
}
} }
/// Propagate a changed value back to the original source. /// Propagate a changed value back to the original source.
/// This has no effect except for string indexing. /// This has no effect except for string indexing.
@ -420,6 +436,36 @@ impl<'a> From<&'a mut Dynamic> for Target<'a> {
} }
} }
impl AsRef<Dynamic> for Target<'_> {
#[inline(always)]
fn as_ref(&self) -> &Dynamic {
match self {
Self::Ref(r) => *r,
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
Self::LockGuard((r, _)) => &**r,
Self::Value(ref r) => r,
#[cfg(not(feature = "no_index"))]
Self::StringChar(_, _, ref r) => r,
}
}
}
impl AsMut<Dynamic> for Target<'_> {
#[inline(always)]
fn as_mut(&mut self) -> &mut Dynamic {
match self {
Self::Ref(r) => *r,
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
Self::LockGuard((r, _)) => r.deref_mut(),
Self::Value(ref mut r) => r,
#[cfg(not(feature = "no_index"))]
Self::StringChar(_, _, ref mut r) => r,
}
}
}
impl<T: Into<Dynamic>> From<T> for Target<'_> { impl<T: Into<Dynamic>> From<T> for Target<'_> {
#[inline(always)] #[inline(always)]
fn from(value: T) -> Self { fn from(value: T) -> Self {
@ -566,17 +612,19 @@ pub struct Engine {
pub(crate) global_module: Module, pub(crate) global_module: Module,
/// A collection of all library packages loaded into the Engine. /// A collection of all library packages loaded into the Engine.
pub(crate) packages: PackagesCollection, pub(crate) packages: PackagesCollection,
/// A collection of all sub-modules directly loaded into the Engine.
pub(crate) global_sub_modules: Imports,
/// A module resolution service. /// A module resolution service.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub(crate) module_resolver: Option<Box<dyn ModuleResolver>>, pub(crate) module_resolver: Option<Box<dyn crate::ModuleResolver>>,
/// A hashmap mapping type names to pretty-print names. /// A hashmap mapping type names to pretty-print names.
pub(crate) type_names: HashMap<String, String>, pub(crate) type_names: HashMap<String, String>,
/// A hashset containing symbols to disable. /// A hashset containing symbols to disable.
pub(crate) disabled_symbols: HashSet<String>, pub(crate) disabled_symbols: HashSet<String>,
/// A hashset containing custom keywords and precedence to recognize. /// A hashmap containing custom keywords and precedence to recognize.
pub(crate) custom_keywords: HashMap<String, Option<u8>>, pub(crate) custom_keywords: HashMap<String, Option<u8>>,
/// Custom syntax. /// Custom syntax.
pub(crate) custom_syntax: HashMap<ImmutableString, CustomSyntax>, pub(crate) custom_syntax: HashMap<ImmutableString, CustomSyntax>,
@ -681,11 +729,12 @@ impl Engine {
packages: Default::default(), packages: Default::default(),
global_module: Default::default(), global_module: Default::default(),
global_sub_modules: Default::default(),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
module_resolver: Some(Box::new(resolvers::FileModuleResolver::new())), module_resolver: Some(Box::new(crate::module::resolvers::FileModuleResolver::new())),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(any(feature = "no_std", target_arch = "wasm32",))] #[cfg(any(feature = "no_std", target_arch = "wasm32",))]
module_resolver: None, module_resolver: None,
@ -743,6 +792,7 @@ impl Engine {
packages: Default::default(), packages: Default::default(),
global_module: Default::default(), global_module: Default::default(),
global_sub_modules: Default::default(),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
module_resolver: None, module_resolver: None,
@ -919,6 +969,8 @@ impl Engine {
// Pop the last index value // Pop the last index value
let idx_val = idx_values.pop().unwrap(); let idx_val = idx_values.pop().unwrap();
let target_val = target.as_mut();
match chain_type { match chain_type {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
ChainType::Index => { ChainType::Index => {
@ -930,7 +982,8 @@ impl Engine {
let idx_pos = x.lhs.position(); let idx_pos = x.lhs.position();
let idx_val = idx_val.as_value(); let idx_val = idx_val.as_value();
let obj_ptr = &mut self.get_indexed_mut( let obj_ptr = &mut self.get_indexed_mut(
mods, state, lib, target, idx_val, idx_pos, false, true, level, mods, state, lib, target_val, idx_val, idx_pos, false, is_ref, true,
level,
)?; )?;
self.eval_dot_index_chain_helper( self.eval_dot_index_chain_helper(
@ -946,7 +999,7 @@ impl Engine {
// `call_setter` is introduced to bypass double mutable borrowing of target // `call_setter` is introduced to bypass double mutable borrowing of target
let _call_setter = match self.get_indexed_mut( let _call_setter = match self.get_indexed_mut(
mods, state, lib, target, idx_val, pos, true, false, level, mods, state, lib, target_val, idx_val, pos, true, is_ref, false, level,
) { ) {
// Indexed value is a reference - update directly // Indexed value is a reference - update directly
Ok(ref mut obj_ptr) => { Ok(ref mut obj_ptr) => {
@ -964,9 +1017,8 @@ impl Engine {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
if let Some(mut new_val) = _call_setter { if let Some(mut new_val) = _call_setter {
let val = target.as_mut(); let val_type_name = target_val.type_name();
let val_type_name = val.type_name(); let args = &mut [target_val, &mut idx_val2, &mut new_val.0];
let args = &mut [val, &mut idx_val2, &mut new_val.0];
self.exec_fn_call( self.exec_fn_call(
mods, state, lib, FN_IDX_SET, 0, args, is_ref, true, false, None, mods, state, lib, FN_IDX_SET, 0, args, is_ref, true, false, None,
@ -991,7 +1043,7 @@ impl Engine {
_ => { _ => {
let idx_val = idx_val.as_value(); let idx_val = idx_val.as_value();
self.get_indexed_mut( self.get_indexed_mut(
mods, state, lib, target, idx_val, pos, false, true, level, mods, state, lib, target_val, idx_val, pos, false, is_ref, true, level,
) )
.map(|v| (v.take_or_clone(), false)) .map(|v| (v.take_or_clone(), false))
} }
@ -1021,22 +1073,22 @@ impl Engine {
// xxx.module::fn_name(...) - syntax error // xxx.module::fn_name(...) - syntax error
Expr::FnCall(_, _) => unreachable!(), Expr::FnCall(_, _) => unreachable!(),
// {xxx:map}.id = ??? // {xxx:map}.id = ???
Expr::Property(x) if target.is::<Map>() && new_val.is_some() => { Expr::Property(x) if target_val.is::<Map>() && new_val.is_some() => {
let IdentX { name, pos } = &x.1; let IdentX { name, pos } = &x.1;
let index = name.clone().into(); let index = name.clone().into();
let mut val = self.get_indexed_mut( let mut val = self.get_indexed_mut(
mods, state, lib, target, index, *pos, true, false, level, mods, state, lib, target_val, index, *pos, true, is_ref, false, level,
)?; )?;
val.set_value(new_val.unwrap())?; val.set_value(new_val.unwrap())?;
Ok((Default::default(), true)) Ok((Default::default(), true))
} }
// {xxx:map}.id // {xxx:map}.id
Expr::Property(x) if target.is::<Map>() => { Expr::Property(x) if target_val.is::<Map>() => {
let IdentX { name, pos } = &x.1; let IdentX { name, pos } = &x.1;
let index = name.clone().into(); let index = name.clone().into();
let val = self.get_indexed_mut( let val = self.get_indexed_mut(
mods, state, lib, target, index, *pos, false, false, level, mods, state, lib, target_val, index, *pos, false, is_ref, false, level,
)?; )?;
Ok((val.take_or_clone(), false)) Ok((val.take_or_clone(), false))
@ -1045,7 +1097,7 @@ impl Engine {
Expr::Property(x) if new_val.is_some() => { Expr::Property(x) if new_val.is_some() => {
let ((_, setter), IdentX { pos, .. }) = x.as_ref(); let ((_, setter), IdentX { pos, .. }) = x.as_ref();
let mut new_val = new_val; let mut new_val = new_val;
let mut args = [target.as_mut(), &mut new_val.as_mut().unwrap().0]; let mut args = [target_val, &mut new_val.as_mut().unwrap().0];
self.exec_fn_call( self.exec_fn_call(
mods, state, lib, setter, 0, &mut args, is_ref, true, false, None, mods, state, lib, setter, 0, &mut args, is_ref, true, false, None,
None, level, None, level,
@ -1056,7 +1108,7 @@ impl Engine {
// xxx.id // xxx.id
Expr::Property(x) => { Expr::Property(x) => {
let ((getter, _), IdentX { pos, .. }) = x.as_ref(); let ((getter, _), IdentX { pos, .. }) = x.as_ref();
let mut args = [target.as_mut()]; let mut args = [target_val];
self.exec_fn_call( self.exec_fn_call(
mods, state, lib, getter, 0, &mut args, is_ref, true, false, None, mods, state, lib, getter, 0, &mut args, is_ref, true, false, None,
None, level, None, level,
@ -1065,13 +1117,14 @@ impl Engine {
.map_err(|err| err.fill_position(*pos)) .map_err(|err| err.fill_position(*pos))
} }
// {xxx:map}.sub_lhs[expr] | {xxx:map}.sub_lhs.expr // {xxx:map}.sub_lhs[expr] | {xxx:map}.sub_lhs.expr
Expr::Index(x, x_pos) | Expr::Dot(x, x_pos) if target.is::<Map>() => { Expr::Index(x, x_pos) | Expr::Dot(x, x_pos) if target_val.is::<Map>() => {
let mut val = match &x.lhs { let mut val = match &x.lhs {
Expr::Property(p) => { Expr::Property(p) => {
let IdentX { name, pos } = &p.1; let IdentX { name, pos } = &p.1;
let index = name.clone().into(); let index = name.clone().into();
self.get_indexed_mut( self.get_indexed_mut(
mods, state, lib, target, index, *pos, false, true, level, mods, state, lib, target_val, index, *pos, false, is_ref, true,
level,
)? )?
} }
// {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr
@ -1111,7 +1164,7 @@ impl Engine {
// xxx.prop[expr] | xxx.prop.expr // xxx.prop[expr] | xxx.prop.expr
Expr::Property(p) => { Expr::Property(p) => {
let ((getter, setter), IdentX { pos, .. }) = p.as_ref(); let ((getter, setter), IdentX { pos, .. }) = p.as_ref();
let arg_values = &mut [target.as_mut(), &mut Default::default()]; let arg_values = &mut [target_val, &mut Default::default()];
let args = &mut arg_values[..1]; let args = &mut arg_values[..1];
let (mut val, updated) = self let (mut val, updated) = self
.exec_fn_call( .exec_fn_call(
@ -1212,7 +1265,7 @@ impl Engine {
level: usize, level: usize,
new_val: Option<(Dynamic, Position)>, new_val: Option<(Dynamic, Position)>,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
let (BinaryExpr { lhs, rhs }, chain_type, op_pos) = match expr { let (crate::ast::BinaryExpr { lhs, rhs }, chain_type, op_pos) = match expr {
Expr::Index(x, pos) => (x.as_ref(), ChainType::Index, *pos), Expr::Index(x, pos) => (x.as_ref(), ChainType::Index, *pos),
Expr::Dot(x, pos) => (x.as_ref(), ChainType::Dot, *pos), Expr::Dot(x, pos) => (x.as_ref(), ChainType::Dot, *pos),
_ => unreachable!(), _ => unreachable!(),
@ -1314,7 +1367,7 @@ impl Engine {
Expr::FnCall(_, _) => unreachable!(), Expr::FnCall(_, _) => unreachable!(),
Expr::Property(_) => idx_values.push(IndexChainValue::None), Expr::Property(_) => idx_values.push(IndexChainValue::None),
Expr::Index(x, _) | Expr::Dot(x, _) => { Expr::Index(x, _) | Expr::Dot(x, _) => {
let BinaryExpr { lhs, rhs, .. } = x.as_ref(); let crate::ast::BinaryExpr { lhs, rhs, .. } = x.as_ref();
// Evaluate in left-to-right order // Evaluate in left-to-right order
let lhs_val = match lhs { let lhs_val = match lhs {
@ -1358,32 +1411,28 @@ impl Engine {
/// Get the value at the indexed position of a base type /// Get the value at the indexed position of a base type
/// Position in `EvalAltResult` may be None and should be set afterwards. /// Position in `EvalAltResult` may be None and should be set afterwards.
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
fn get_indexed_mut<'a>( fn get_indexed_mut<'t>(
&self, &self,
_mods: &mut Imports, _mods: &mut Imports,
state: &mut State, state: &mut State,
_lib: &[&Module], _lib: &[&Module],
target: &'a mut Target, target: &'t mut Dynamic,
idx: Dynamic, idx: Dynamic,
idx_pos: Position, idx_pos: Position,
_create: bool, _create: bool,
_is_ref: bool,
_indexers: bool, _indexers: bool,
_level: usize, _level: usize,
) -> Result<Target<'a>, Box<EvalAltResult>> { ) -> Result<Target<'t>, Box<EvalAltResult>> {
self.inc_operations(state)?; self.inc_operations(state)?;
#[cfg(not(feature = "no_index"))] match target {
let is_ref = target.is_ref();
let val = target.as_mut();
match val {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(arr)) => { Dynamic(Union::Array(arr)) => {
// val_array[idx] // val_array[idx]
let index = idx let index = idx
.as_int() .as_int()
.map_err(|err| self.make_type_mismatch_err::<INT>(err, idx_pos))?; .map_err(|err| self.make_type_mismatch_err::<crate::INT>(err, idx_pos))?;
let arr_len = arr.len(); let arr_len = arr.len();
@ -1424,14 +1473,14 @@ impl Engine {
let chars_len = s.chars().count(); let chars_len = s.chars().count();
let index = idx let index = idx
.as_int() .as_int()
.map_err(|err| self.make_type_mismatch_err::<INT>(err, idx_pos))?; .map_err(|err| self.make_type_mismatch_err::<crate::INT>(err, idx_pos))?;
if index >= 0 { if index >= 0 {
let offset = index as usize; let offset = index as usize;
let ch = s.chars().nth(offset).ok_or_else(|| { let ch = s.chars().nth(offset).ok_or_else(|| {
EvalAltResult::ErrorStringBounds(chars_len, index, idx_pos) EvalAltResult::ErrorStringBounds(chars_len, index, idx_pos)
})?; })?;
Ok(Target::StringChar(val, offset, ch.into())) Ok(Target::StringChar(target, offset, ch.into()))
} else { } else {
EvalAltResult::ErrorStringBounds(chars_len, index, idx_pos).into() EvalAltResult::ErrorStringBounds(chars_len, index, idx_pos).into()
} }
@ -1439,11 +1488,11 @@ impl Engine {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
_ if _indexers => { _ if _indexers => {
let type_name = val.type_name(); let type_name = target.type_name();
let mut idx = idx; let mut idx = idx;
let args = &mut [val, &mut idx]; let args = &mut [target, &mut idx];
self.exec_fn_call( self.exec_fn_call(
_mods, state, _lib, FN_IDX_GET, 0, args, is_ref, true, false, None, None, _mods, state, _lib, FN_IDX_GET, 0, args, _is_ref, true, false, None, None,
_level, _level,
) )
.map(|(v, _)| v.into()) .map(|(v, _)| v.into())
@ -1455,10 +1504,11 @@ impl Engine {
}) })
} }
_ => { _ => EvalAltResult::ErrorIndexingType(
EvalAltResult::ErrorIndexingType(self.map_type_name(val.type_name()).into(), NO_POS) self.map_type_name(target.type_name()).into(),
.into() NO_POS,
} )
.into(),
} }
} }
@ -1526,6 +1576,119 @@ impl Engine {
} }
} }
/// Get a `Target` from an expression.
pub(crate) fn eval_expr_as_target<'s>(
&self,
scope: &'s mut Scope,
mods: &mut Imports,
state: &mut State,
lib: &[&Module],
this_ptr: &'s mut Option<&mut Dynamic>,
expr: &Expr,
no_const: bool,
level: usize,
) -> Result<(Target<'s>, Position), Box<EvalAltResult>> {
match expr {
// var - point directly to the value
Expr::Variable(_) => {
let (target, _, typ, pos) =
self.search_namespace(scope, mods, state, lib, this_ptr, expr)?;
Ok((
match typ {
// If necessary, constants are cloned
ScopeEntryType::Constant if no_const => target.into_owned(),
_ => target,
},
pos,
))
}
// var[...]
#[cfg(not(feature = "no_index"))]
Expr::Index(x, _) if x.lhs.get_variable_access(false).is_some() => match x.rhs {
Expr::Property(_) => unreachable!(),
// var[...]...
Expr::FnCall(_, _) | Expr::Index(_, _) | Expr::Dot(_, _) => self
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
.map(|v| (v.into(), expr.position())),
// var[expr] - point directly to the item
_ => {
let idx = self.eval_expr(scope, mods, state, lib, this_ptr, &x.rhs, level)?;
let idx_pos = x.rhs.position();
let (mut target, pos) = self.eval_expr_as_target(
scope, mods, state, lib, this_ptr, &x.lhs, no_const, level,
)?;
let is_ref = target.is_ref();
if target.is_shared() || target.is_value() {
let target_ref = target.as_mut();
self.get_indexed_mut(
mods, state, lib, target_ref, idx, idx_pos, false, is_ref, true, level,
)
.map(Target::into_owned)
} else {
let target_ref = target.take_ref().unwrap();
self.get_indexed_mut(
mods, state, lib, target_ref, idx, idx_pos, false, is_ref, true, level,
)
}
.map(|v| (v, pos))
}
},
// var.prop
#[cfg(not(feature = "no_object"))]
Expr::Dot(x, _) if x.lhs.get_variable_access(false).is_some() => match x.rhs {
Expr::Variable(_) => unreachable!(),
// var.prop
Expr::Property(ref p) => {
let (mut target, _) = self.eval_expr_as_target(
scope, mods, state, lib, this_ptr, &x.lhs, no_const, level,
)?;
let is_ref = target.is_ref();
if target.is::<Map>() {
// map.prop - point directly to the item
let (_, IdentX { name, pos }) = p.as_ref();
let idx = name.clone().into();
if target.is_shared() || target.is_value() {
let target_ref = target.as_mut();
self.get_indexed_mut(
mods, state, lib, target_ref, idx, *pos, false, is_ref, true, level,
)
.map(Target::into_owned)
} else {
let target_ref = target.take_ref().unwrap();
self.get_indexed_mut(
mods, state, lib, target_ref, idx, *pos, false, is_ref, true, level,
)
}
.map(|v| (v, *pos))
} else {
// var.prop - call property getter
let ((getter, _), IdentX { pos, .. }) = p.as_ref();
let mut args = [target.as_mut()];
self.exec_fn_call(
mods, state, lib, getter, 0, &mut args, is_ref, true, false, None,
None, level,
)
.map(|(v, _)| (v.into(), *pos))
.map_err(|err| err.fill_position(*pos))
}
}
// var.???
_ => self
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
.map(|v| (v.into(), expr.position())),
},
// expr
_ => self
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
.map(|v| (v.into(), expr.position())),
}
}
/// Evaluate an expression /// Evaluate an expression
pub(crate) fn eval_expr( pub(crate) fn eval_expr(
&self, &self,
@ -1543,6 +1706,7 @@ impl Engine {
let result = match expr { let result = match expr {
Expr::Expr(x) => self.eval_expr(scope, mods, state, lib, this_ptr, x, level), Expr::Expr(x) => self.eval_expr(scope, mods, state, lib, this_ptr, x, level),
Expr::DynamicConstant(x, _) => Ok(x.as_ref().clone()),
Expr::IntegerConstant(x, _) => Ok((*x).into()), Expr::IntegerConstant(x, _) => Ok((*x).into()),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Expr::FloatConstant(x, _) => Ok((*x).into()), Expr::FloatConstant(x, _) => Ok((*x).into()),
@ -1581,21 +1745,27 @@ impl Engine {
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Array(x, _) => Ok(Dynamic(Union::Array(Box::new( Expr::Array(x, _) => {
x.iter() let mut arr =
.map(|item| self.eval_expr(scope, mods, state, lib, this_ptr, item, level)) Array::with_capacity(crate::stdlib::cmp::max(TYPICAL_ARRAY_SIZE, x.len()));
.collect::<Result<Vec<_>, _>>()?, for item in x.as_ref() {
)))), arr.push(self.eval_expr(scope, mods, state, lib, this_ptr, item, level)?);
}
Ok(Dynamic(Union::Array(Box::new(arr))))
}
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Map(x, _) => Ok(Dynamic(Union::Map(Box::new( Expr::Map(x, _) => {
x.iter() let mut map =
.map(|(key, expr)| { Map::with_capacity(crate::stdlib::cmp::max(TYPICAL_MAP_SIZE, x.len()));
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) for (IdentX { name: key, .. }, expr) in x.as_ref() {
.map(|val| (key.name.clone(), val)) map.insert(
}) key.clone(),
.collect::<Result<HashMap<_, _>, _>>()?, self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?,
)))), );
}
Ok(Dynamic(Union::Map(Box::new(map))))
}
// Normal function call // Normal function call
Expr::FnCall(x, pos) if x.namespace.is_none() => { Expr::FnCall(x, pos) if x.namespace.is_none() => {
@ -1666,26 +1836,7 @@ impl Engine {
Expr::True(_) => Ok(true.into()), Expr::True(_) => Ok(true.into()),
Expr::False(_) => Ok(false.into()), Expr::False(_) => Ok(false.into()),
Expr::Unit(_) => Ok(().into()), Expr::Unit(_) => Ok(Dynamic::UNIT),
Expr::Switch(x, _) => {
let (match_expr, table, def_stmt) = x.as_ref();
let match_item =
self.eval_expr(scope, mods, state, lib, this_ptr, match_expr, level)?;
let hasher = &mut get_hasher();
match_item.hash(hasher);
let hash = hasher.finish();
if let Some(stmt) = table.get(&hash) {
self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level)
} else if let Some(def_stmt) = def_stmt {
self.eval_stmt(scope, mods, state, lib, this_ptr, def_stmt, level)
} else {
Ok(().into())
}
}
Expr::Custom(custom, _) => { Expr::Custom(custom, _) => {
let expressions = custom let expressions = custom
@ -1814,7 +1965,8 @@ impl Engine {
match self match self
.global_module .global_module
.get_fn(hash_fn, false) .get_fn(hash_fn, false)
.or_else(|| self.packages.get_fn(hash_fn, false)) .or_else(|| self.packages.get_fn(hash_fn))
.or_else(|| mods.get_fn(hash_fn))
{ {
// op= function registered as method // op= function registered as method
Some(func) if func.is_method() => { Some(func) if func.is_method() => {
@ -1832,9 +1984,10 @@ impl Engine {
// Overriding exact implementation // Overriding exact implementation
if func.is_plugin_fn() { if func.is_plugin_fn() {
func.get_plugin_fn().call((self, mods, lib).into(), args)?; func.get_plugin_fn()
.call((self, &*mods, lib).into(), args)?;
} else { } else {
func.get_native_fn()((self, mods, lib).into(), args)?; func.get_native_fn()((self, &*mods, lib).into(), args)?;
} }
} }
// Built-in op-assignment function // Built-in op-assignment function
@ -1926,8 +2079,8 @@ impl Engine {
self.eval_statements(scope, mods, state, lib, this_ptr, statements, level) self.eval_statements(scope, mods, state, lib, this_ptr, statements, level)
} }
// If-else statement // If statement
Stmt::IfThenElse(expr, x, _) => { Stmt::If(expr, x, _) => {
let (if_block, else_block) = x.as_ref(); let (if_block, else_block) = x.as_ref();
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.as_bool() .as_bool()
@ -1943,6 +2096,28 @@ impl Engine {
}) })
} }
// Switch statement
Stmt::Switch(match_expr, x, _) => {
let (table, def_stmt) = x.as_ref();
let hasher = &mut get_hasher();
self.eval_expr_as_target(
scope, mods, state, lib, this_ptr, match_expr, false, level,
)?
.0
.as_ref()
.hash(hasher);
let hash = hasher.finish();
if let Some(stmt) = table.get(&hash) {
self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level)
} else if let Some(def_stmt) = def_stmt {
self.eval_stmt(scope, mods, state, lib, this_ptr, def_stmt, level)
} else {
Ok(Dynamic::UNIT)
}
}
// While loop // While loop
Stmt::While(expr, body, _) => loop { Stmt::While(expr, body, _) => loop {
match self match self
@ -1987,7 +2162,8 @@ impl Engine {
let func = self let func = self
.global_module .global_module
.get_iter(iter_type) .get_iter(iter_type)
.or_else(|| self.packages.get_iter(iter_type)); .or_else(|| self.packages.get_iter(iter_type))
.or_else(|| mods.get_iter(iter_type));
if let Some(func) = func { if let Some(func) = func {
// Add the loop variable // Add the loop variable
@ -2094,25 +2270,25 @@ impl Engine {
} }
// Return value // Return value
Stmt::ReturnWithVal((ReturnType::Return, pos), Some(expr), _) => EvalAltResult::Return( Stmt::Return((ReturnType::Return, pos), Some(expr), _) => EvalAltResult::Return(
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?, self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?,
*pos, *pos,
) )
.into(), .into(),
// Empty return // Empty return
Stmt::ReturnWithVal((ReturnType::Return, pos), None, _) => { Stmt::Return((ReturnType::Return, pos), None, _) => {
EvalAltResult::Return(Default::default(), *pos).into() EvalAltResult::Return(Default::default(), *pos).into()
} }
// Throw value // Throw value
Stmt::ReturnWithVal((ReturnType::Exception, pos), Some(expr), _) => { Stmt::Return((ReturnType::Exception, pos), Some(expr), _) => {
let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
EvalAltResult::ErrorRuntime(val, *pos).into() EvalAltResult::ErrorRuntime(val, *pos).into()
} }
// Empty throw // Empty throw
Stmt::ReturnWithVal((ReturnType::Exception, pos), None, _) => { Stmt::Return((ReturnType::Exception, pos), None, _) => {
EvalAltResult::ErrorRuntime(().into(), *pos).into() EvalAltResult::ErrorRuntime(().into(), *pos).into()
} }
@ -2172,7 +2348,7 @@ impl Engine {
if let Some(name_def) = alias { if let Some(name_def) = alias {
if !module.is_indexed() { if !module.is_indexed() {
// Index the module (making a clone copy if necessary) if it is not indexed // Index the module (making a clone copy if necessary) if it is not indexed
let mut module = shared_take_or_clone(module); let mut module = crate::fn_native::shared_take_or_clone(module);
module.build_index(); module.build_index();
mods.push(name_def.name.clone(), module); mods.push(name_def.name.clone(), module);
} else { } else {
@ -2219,7 +2395,7 @@ impl Engine {
if !val.is_shared() { if !val.is_shared() {
// Replace the variable with a shared value. // Replace the variable with a shared value.
*val = mem::take(val).into_shared(); *val = crate::stdlib::mem::take(val).into_shared();
} }
} }
_ => (), _ => (),

View File

@ -8,30 +8,14 @@ use crate::optimize::OptimizationLevel;
use crate::parse_error::ParseError; use crate::parse_error::ParseError;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::Scope; use crate::scope::Scope;
use crate::token::{Position, NO_POS}; use crate::token::NO_POS;
use crate::utils::get_hasher; use crate::utils::get_hasher;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::{ use crate::Array;
engine::{Array, FN_IDX_GET, FN_IDX_SET},
utils::ImmutableString,
};
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::{ use crate::Map;
engine::{make_getter, make_setter, Map},
parse_error::ParseErrorType,
token::Token,
};
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
use crate::fn_register::{RegisterFn, RegisterResultFn};
#[cfg(not(feature = "no_function"))]
use crate::{fn_args::FuncArgs, fn_call::ensure_no_data_race, module::Module, StaticVec};
#[cfg(not(feature = "no_optimize"))]
use crate::optimize::optimize_into_ast;
use crate::stdlib::{ use crate::stdlib::{
any::{type_name, TypeId}, any::{type_name, TypeId},
@ -40,9 +24,6 @@ use crate::stdlib::{
string::String, string::String,
}; };
#[cfg(not(feature = "no_optimize"))]
use crate::stdlib::mem;
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf}; use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf};
@ -84,7 +65,6 @@ impl Engine {
self.global_module.set_raw_fn(name, arg_types, func); self.global_module.set_raw_fn(name, arg_types, func);
self self
} }
/// Register a custom type for use with the `Engine`. /// Register a custom type for use with the `Engine`.
/// The type must implement `Clone`. /// The type must implement `Clone`.
/// ///
@ -126,7 +106,6 @@ impl Engine {
pub fn register_type<T: Variant + Clone>(&mut self) -> &mut Self { pub fn register_type<T: Variant + Clone>(&mut self) -> &mut Self {
self.register_type_with_name::<T>(type_name::<T>()) self.register_type_with_name::<T>(type_name::<T>())
} }
/// Register a custom type for use with the `Engine`, with a pretty-print name /// Register a custom type for use with the `Engine`, with a pretty-print name
/// for the `type_of` function. The type must implement `Clone`. /// for the `type_of` function. The type must implement `Clone`.
/// ///
@ -177,7 +156,6 @@ impl Engine {
self.type_names.insert(type_name::<T>().into(), name.into()); self.type_names.insert(type_name::<T>().into(), name.into());
self self
} }
/// Register an iterator adapter for an iterable type with the `Engine`. /// Register an iterator adapter for an iterable type with the `Engine`.
/// This is an advanced feature. /// This is an advanced feature.
#[inline(always)] #[inline(always)]
@ -189,7 +167,6 @@ impl Engine {
self.global_module.set_iterable::<T>(); self.global_module.set_iterable::<T>();
self self
} }
/// Register a getter function for a member of a registered type with the `Engine`. /// Register a getter function for a member of a registered type with the `Engine`.
/// ///
/// The function signature must start with `&mut self` and not `&self`. /// The function signature must start with `&mut self` and not `&self`.
@ -236,9 +213,8 @@ impl Engine {
T: Variant + Clone, T: Variant + Clone,
U: Variant + Clone, U: Variant + Clone,
{ {
self.register_fn(&make_getter(name), callback) crate::RegisterFn::register_fn(self, &crate::engine::make_getter(name), callback)
} }
/// Register a getter function for a member of a registered type with the `Engine`. /// Register a getter function for a member of a registered type with the `Engine`.
/// Returns `Result<Dynamic, Box<EvalAltResult>>`. /// Returns `Result<Dynamic, Box<EvalAltResult>>`.
/// ///
@ -284,9 +260,12 @@ impl Engine {
name: &str, name: &str,
callback: impl Fn(&mut T) -> Result<Dynamic, Box<EvalAltResult>> + SendSync + 'static, callback: impl Fn(&mut T) -> Result<Dynamic, Box<EvalAltResult>> + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
self.register_result_fn(&make_getter(name), callback) crate::RegisterResultFn::register_result_fn(
self,
&crate::engine::make_getter(name),
callback,
)
} }
/// Register a setter function for a member of a registered type with the `Engine`. /// Register a setter function for a member of a registered type with the `Engine`.
/// ///
/// # Example /// # Example
@ -334,9 +313,8 @@ impl Engine {
T: Variant + Clone, T: Variant + Clone,
U: Variant + Clone, U: Variant + Clone,
{ {
self.register_fn(&make_setter(name), callback) crate::RegisterFn::register_fn(self, &crate::engine::make_setter(name), callback)
} }
/// Register a setter function for a member of a registered type with the `Engine`. /// Register a setter function for a member of a registered type with the `Engine`.
/// Returns `Result<(), Box<EvalAltResult>>`. /// Returns `Result<(), Box<EvalAltResult>>`.
/// ///
@ -388,11 +366,12 @@ impl Engine {
T: Variant + Clone, T: Variant + Clone,
U: Variant + Clone, U: Variant + Clone,
{ {
self.register_result_fn(&make_setter(name), move |obj: &mut T, value: U| { crate::RegisterResultFn::register_result_fn(
callback(obj, value).map(Into::into) self,
}) &crate::engine::make_setter(name),
move |obj: &mut T, value: U| callback(obj, value).map(Into::into),
)
} }
/// Short-hand for registering both getter and setter functions /// Short-hand for registering both getter and setter functions
/// of a registered type with the `Engine`. /// of a registered type with the `Engine`.
/// ///
@ -445,7 +424,6 @@ impl Engine {
{ {
self.register_get(name, get_fn).register_set(name, set_fn) self.register_get(name, get_fn).register_set(name, set_fn)
} }
/// Register an index getter for a custom type with the `Engine`. /// Register an index getter for a custom type with the `Engine`.
/// ///
/// The function signature must start with `&mut self` and not `&self`. /// The function signature must start with `&mut self` and not `&self`.
@ -507,14 +485,13 @@ impl Engine {
} }
if TypeId::of::<T>() == TypeId::of::<String>() if TypeId::of::<T>() == TypeId::of::<String>()
|| TypeId::of::<T>() == TypeId::of::<&str>() || TypeId::of::<T>() == TypeId::of::<&str>()
|| TypeId::of::<T>() == TypeId::of::<ImmutableString>() || TypeId::of::<T>() == TypeId::of::<crate::ImmutableString>()
{ {
panic!("Cannot register indexer for strings."); panic!("Cannot register indexer for strings.");
} }
self.register_fn(FN_IDX_GET, callback) crate::RegisterFn::register_fn(self, crate::engine::FN_IDX_GET, callback)
} }
/// Register an index getter for a custom type with the `Engine`. /// Register an index getter for a custom type with the `Engine`.
/// Returns `Result<Dynamic, Box<EvalAltResult>>`. /// Returns `Result<Dynamic, Box<EvalAltResult>>`.
/// ///
@ -578,14 +555,13 @@ impl Engine {
} }
if TypeId::of::<T>() == TypeId::of::<String>() if TypeId::of::<T>() == TypeId::of::<String>()
|| TypeId::of::<T>() == TypeId::of::<&str>() || TypeId::of::<T>() == TypeId::of::<&str>()
|| TypeId::of::<T>() == TypeId::of::<ImmutableString>() || TypeId::of::<T>() == TypeId::of::<crate::ImmutableString>()
{ {
panic!("Cannot register indexer for strings."); panic!("Cannot register indexer for strings.");
} }
self.register_result_fn(FN_IDX_GET, callback) crate::RegisterResultFn::register_result_fn(self, crate::engine::FN_IDX_GET, callback)
} }
/// Register an index setter for a custom type with the `Engine`. /// Register an index setter for a custom type with the `Engine`.
/// ///
/// # Panics /// # Panics
@ -647,14 +623,13 @@ impl Engine {
} }
if TypeId::of::<T>() == TypeId::of::<String>() if TypeId::of::<T>() == TypeId::of::<String>()
|| TypeId::of::<T>() == TypeId::of::<&str>() || TypeId::of::<T>() == TypeId::of::<&str>()
|| TypeId::of::<T>() == TypeId::of::<ImmutableString>() || TypeId::of::<T>() == TypeId::of::<crate::ImmutableString>()
{ {
panic!("Cannot register indexer for strings."); panic!("Cannot register indexer for strings.");
} }
self.register_fn(FN_IDX_SET, callback) crate::RegisterFn::register_fn(self, crate::engine::FN_IDX_SET, callback)
} }
/// Register an index setter for a custom type with the `Engine`. /// Register an index setter for a custom type with the `Engine`.
/// Returns `Result<(), Box<EvalAltResult>>`. /// Returns `Result<(), Box<EvalAltResult>>`.
/// ///
@ -720,16 +695,17 @@ impl Engine {
} }
if TypeId::of::<T>() == TypeId::of::<String>() if TypeId::of::<T>() == TypeId::of::<String>()
|| TypeId::of::<T>() == TypeId::of::<&str>() || TypeId::of::<T>() == TypeId::of::<&str>()
|| TypeId::of::<T>() == TypeId::of::<ImmutableString>() || TypeId::of::<T>() == TypeId::of::<crate::ImmutableString>()
{ {
panic!("Cannot register indexer for strings."); panic!("Cannot register indexer for strings.");
} }
self.register_result_fn(FN_IDX_SET, move |obj: &mut T, index: X, value: U| { crate::RegisterResultFn::register_result_fn(
callback(obj, index, value).map(Into::into) self,
}) crate::engine::FN_IDX_SET,
move |obj: &mut T, index: X, value: U| callback(obj, index, value).map(Into::into),
)
} }
/// Short-hand for register both index getter and setter functions for a custom type with the `Engine`. /// Short-hand for register both index getter and setter functions for a custom type with the `Engine`.
/// ///
/// # Panics /// # Panics
@ -785,7 +761,45 @@ impl Engine {
self.register_indexer_get(getter) self.register_indexer_get(getter)
.register_indexer_set(setter) .register_indexer_set(setter)
} }
/// Register a `Module` as a sub-module with the `Engine`.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// use rhai::{Engine, Module};
///
/// let mut engine = Engine::new();
///
/// // Create the module
/// let mut module = Module::new();
/// module.set_fn_1("calc", |x: i64| Ok(x + 1));
///
/// // Register the module as a sub-module
/// engine.register_module("CalcService", module);
///
/// assert_eq!(engine.eval::<i64>("CalcService::calc(41)")?, 42);
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "no_module"))]
pub fn register_module(
&mut self,
name: impl Into<crate::ImmutableString>,
module: impl Into<crate::Shared<crate::Module>>,
) -> &mut Self {
let module = module.into();
if !module.is_indexed() {
// Index the module (making a clone copy if necessary) if it is not indexed
let mut module = crate::fn_native::shared_take_or_clone(module);
module.build_index();
self.global_sub_modules.push_fixed(name, module);
} else {
self.global_sub_modules.push_fixed(name, module);
}
self
}
/// Compile a string into an `AST`, which can be used later for evaluation. /// Compile a string into an `AST`, which can be used later for evaluation.
/// ///
/// # Example /// # Example
@ -809,7 +823,6 @@ impl Engine {
pub fn compile(&self, script: &str) -> Result<AST, ParseError> { pub fn compile(&self, script: &str) -> Result<AST, ParseError> {
self.compile_with_scope(&Default::default(), script) self.compile_with_scope(&Default::default(), script)
} }
/// Compile a string into an `AST` using own scope, which can be used later for evaluation. /// Compile a string into an `AST` using own scope, which can be used later for evaluation.
/// ///
/// The scope is useful for passing constants into the script for optimization /// The scope is useful for passing constants into the script for optimization
@ -852,7 +865,6 @@ impl Engine {
pub fn compile_with_scope(&self, scope: &Scope, script: &str) -> Result<AST, ParseError> { pub fn compile_with_scope(&self, scope: &Scope, script: &str) -> Result<AST, ParseError> {
self.compile_scripts_with_scope(scope, &[script]) self.compile_scripts_with_scope(scope, &[script])
} }
/// When passed a list of strings, first join the strings into one large script, /// When passed a list of strings, first join the strings into one large script,
/// and then compile them into an `AST` using own scope, which can be used later for evaluation. /// and then compile them into an `AST` using own scope, which can be used later for evaluation.
/// ///
@ -907,7 +919,6 @@ impl Engine {
) -> Result<AST, ParseError> { ) -> Result<AST, ParseError> {
self.compile_with_scope_and_optimization_level(scope, scripts, self.optimization_level) self.compile_with_scope_and_optimization_level(scope, scripts, self.optimization_level)
} }
/// Join a list of strings and compile into an `AST` using own scope at a specific optimization level. /// Join a list of strings and compile into an `AST` using own scope at a specific optimization level.
#[inline(always)] #[inline(always)]
pub(crate) fn compile_with_scope_and_optimization_level( pub(crate) fn compile_with_scope_and_optimization_level(
@ -920,7 +931,6 @@ impl Engine {
let stream = self.lex(scripts, None); let stream = self.lex(scripts, None);
self.parse(hash, &mut stream.peekable(), scope, optimization_level) self.parse(hash, &mut stream.peekable(), scope, optimization_level)
} }
/// Read the contents of a file into a string. /// Read the contents of a file into a string.
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
@ -944,7 +954,6 @@ impl Engine {
Ok(contents) Ok(contents)
} }
/// Compile a script file into an `AST`, which can be used later for evaluation. /// Compile a script file into an `AST`, which can be used later for evaluation.
/// ///
/// # Example /// # Example
@ -971,7 +980,6 @@ impl Engine {
pub fn compile_file(&self, path: PathBuf) -> Result<AST, Box<EvalAltResult>> { pub fn compile_file(&self, path: PathBuf) -> Result<AST, Box<EvalAltResult>> {
self.compile_file_with_scope(&Default::default(), path) self.compile_file_with_scope(&Default::default(), path)
} }
/// Compile a script file into an `AST` using own scope, which can be used later for evaluation. /// Compile a script file into an `AST` using own scope, which can be used later for evaluation.
/// ///
/// The scope is useful for passing constants into the script for optimization /// The scope is useful for passing constants into the script for optimization
@ -1013,7 +1021,6 @@ impl Engine {
) -> Result<AST, Box<EvalAltResult>> { ) -> Result<AST, Box<EvalAltResult>> {
Self::read_file(path).and_then(|contents| Ok(self.compile_with_scope(scope, &contents)?)) Self::read_file(path).and_then(|contents| Ok(self.compile_with_scope(scope, &contents)?))
} }
/// Parse a JSON string into a map. /// Parse a JSON string into a map.
/// ///
/// The JSON string must be an object hash. It cannot be a simple JavaScript primitive. /// The JSON string must be an object hash. It cannot be a simple JavaScript primitive.
@ -1054,6 +1061,8 @@ impl Engine {
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub fn parse_json(&self, json: &str, has_null: bool) -> Result<Map, Box<EvalAltResult>> { pub fn parse_json(&self, json: &str, has_null: bool) -> Result<Map, Box<EvalAltResult>> {
use crate::token::{Position, Token};
let mut scope = Default::default(); let mut scope = Default::default();
// Trims the JSON string and add a '#' in front // Trims the JSON string and add a '#' in front
@ -1063,7 +1072,7 @@ impl Engine {
} else if json_text.starts_with(Token::LeftBrace.syntax().as_ref()) { } else if json_text.starts_with(Token::LeftBrace.syntax().as_ref()) {
["#", json_text] ["#", json_text]
} else { } else {
return Err(ParseErrorType::MissingToken( return Err(crate::ParseErrorType::MissingToken(
Token::LeftBrace.syntax().into(), Token::LeftBrace.syntax().into(),
"to start a JSON object hash".into(), "to start a JSON object hash".into(),
) )
@ -1098,7 +1107,6 @@ impl Engine {
self.eval_ast_with_scope(&mut scope, &ast) self.eval_ast_with_scope(&mut scope, &ast)
} }
/// Compile a string containing an expression into an `AST`, /// Compile a string containing an expression into an `AST`,
/// which can be used later for evaluation. /// which can be used later for evaluation.
/// ///
@ -1123,7 +1131,6 @@ impl Engine {
pub fn compile_expression(&self, script: &str) -> Result<AST, ParseError> { pub fn compile_expression(&self, script: &str) -> Result<AST, ParseError> {
self.compile_expression_with_scope(&Default::default(), script) self.compile_expression_with_scope(&Default::default(), script)
} }
/// Compile a string containing an expression into an `AST` using own scope, /// Compile a string containing an expression into an `AST` using own scope,
/// which can be used later for evaluation. /// which can be used later for evaluation.
/// ///
@ -1176,7 +1183,6 @@ impl Engine {
let mut peekable = stream.peekable(); let mut peekable = stream.peekable();
self.parse_global_expr(hash, &mut peekable, scope, self.optimization_level) self.parse_global_expr(hash, &mut peekable, scope, self.optimization_level)
} }
/// Evaluate a script file. /// Evaluate a script file.
/// ///
/// # Example /// # Example
@ -1198,7 +1204,6 @@ impl Engine {
pub fn eval_file<T: Variant + Clone>(&self, path: PathBuf) -> Result<T, Box<EvalAltResult>> { pub fn eval_file<T: Variant + Clone>(&self, path: PathBuf) -> Result<T, Box<EvalAltResult>> {
Self::read_file(path).and_then(|contents| self.eval::<T>(&contents)) Self::read_file(path).and_then(|contents| self.eval::<T>(&contents))
} }
/// Evaluate a script file with own scope. /// Evaluate a script file with own scope.
/// ///
/// # Example /// # Example
@ -1228,7 +1233,6 @@ impl Engine {
) -> Result<T, Box<EvalAltResult>> { ) -> Result<T, Box<EvalAltResult>> {
Self::read_file(path).and_then(|contents| self.eval_with_scope::<T>(scope, &contents)) Self::read_file(path).and_then(|contents| self.eval_with_scope::<T>(scope, &contents))
} }
/// Evaluate a string. /// Evaluate a string.
/// ///
/// # Example /// # Example
@ -1247,7 +1251,6 @@ impl Engine {
pub fn eval<T: Variant + Clone>(&self, script: &str) -> Result<T, Box<EvalAltResult>> { pub fn eval<T: Variant + Clone>(&self, script: &str) -> Result<T, Box<EvalAltResult>> {
self.eval_with_scope(&mut Default::default(), script) self.eval_with_scope(&mut Default::default(), script)
} }
/// Evaluate a string with own scope. /// Evaluate a string with own scope.
/// ///
/// # Example /// # Example
@ -1283,7 +1286,6 @@ impl Engine {
)?; )?;
self.eval_ast_with_scope(scope, &ast) self.eval_ast_with_scope(scope, &ast)
} }
/// Evaluate a string containing an expression. /// Evaluate a string containing an expression.
/// ///
/// # Example /// # Example
@ -1305,7 +1307,6 @@ impl Engine {
) -> Result<T, Box<EvalAltResult>> { ) -> Result<T, Box<EvalAltResult>> {
self.eval_expression_with_scope(&mut Default::default(), script) self.eval_expression_with_scope(&mut Default::default(), script)
} }
/// Evaluate a string containing an expression with own scope. /// Evaluate a string containing an expression with own scope.
/// ///
/// # Example /// # Example
@ -1340,7 +1341,6 @@ impl Engine {
self.eval_ast_with_scope(scope, &ast) self.eval_ast_with_scope(scope, &ast)
} }
/// Evaluate an `AST`. /// Evaluate an `AST`.
/// ///
/// # Example /// # Example
@ -1363,7 +1363,6 @@ impl Engine {
pub fn eval_ast<T: Variant + Clone>(&self, ast: &AST) -> Result<T, Box<EvalAltResult>> { pub fn eval_ast<T: Variant + Clone>(&self, ast: &AST) -> Result<T, Box<EvalAltResult>> {
self.eval_ast_with_scope(&mut Default::default(), ast) self.eval_ast_with_scope(&mut Default::default(), ast)
} }
/// Evaluate an `AST` with own scope. /// Evaluate an `AST` with own scope.
/// ///
/// # Example /// # Example
@ -1399,7 +1398,8 @@ impl Engine {
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
) -> Result<T, Box<EvalAltResult>> { ) -> Result<T, Box<EvalAltResult>> {
let mut mods = Default::default(); let mut mods = self.global_sub_modules.clone();
let (result, _) = self.eval_ast_with_scope_raw(scope, &mut mods, ast)?; let (result, _) = self.eval_ast_with_scope_raw(scope, &mut mods, ast)?;
let typ = self.map_type_name(result.type_name()); let typ = self.map_type_name(result.type_name());
@ -1413,7 +1413,6 @@ impl Engine {
.into() .into()
}); });
} }
/// Evaluate an `AST` with own scope. /// Evaluate an `AST` with own scope.
#[inline(always)] #[inline(always)]
pub(crate) fn eval_ast_with_scope_raw<'a>( pub(crate) fn eval_ast_with_scope_raw<'a>(
@ -1424,7 +1423,6 @@ impl Engine {
) -> Result<(Dynamic, u64), Box<EvalAltResult>> { ) -> Result<(Dynamic, u64), Box<EvalAltResult>> {
self.eval_statements_raw(scope, mods, ast.statements(), &[ast.lib()]) self.eval_statements_raw(scope, mods, ast.statements(), &[ast.lib()])
} }
/// Evaluate a file, but throw away the result and only return error (if any). /// Evaluate a file, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
@ -1433,7 +1431,6 @@ impl Engine {
pub fn consume_file(&self, path: PathBuf) -> Result<(), Box<EvalAltResult>> { pub fn consume_file(&self, path: PathBuf) -> Result<(), Box<EvalAltResult>> {
Self::read_file(path).and_then(|contents| self.consume(&contents)) Self::read_file(path).and_then(|contents| self.consume(&contents))
} }
/// Evaluate a file with own scope, but throw away the result and only return error (if any). /// Evaluate a file with own scope, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
@ -1446,14 +1443,12 @@ impl Engine {
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
Self::read_file(path).and_then(|contents| self.consume_with_scope(scope, &contents)) Self::read_file(path).and_then(|contents| self.consume_with_scope(scope, &contents))
} }
/// Evaluate a string, but throw away the result and only return error (if any). /// Evaluate a string, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
#[inline(always)] #[inline(always)]
pub fn consume(&self, script: &str) -> Result<(), Box<EvalAltResult>> { pub fn consume(&self, script: &str) -> Result<(), Box<EvalAltResult>> {
self.consume_with_scope(&mut Default::default(), script) self.consume_with_scope(&mut Default::default(), script)
} }
/// Evaluate a string with own scope, but throw away the result and only return error (if any). /// Evaluate a string with own scope, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
#[inline] #[inline]
@ -1468,14 +1463,12 @@ impl Engine {
let ast = self.parse(hash, &mut stream.peekable(), scope, self.optimization_level)?; let ast = self.parse(hash, &mut stream.peekable(), scope, self.optimization_level)?;
self.consume_ast_with_scope(scope, &ast) self.consume_ast_with_scope(scope, &ast)
} }
/// Evaluate an AST, but throw away the result and only return error (if any). /// Evaluate an AST, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
#[inline(always)] #[inline(always)]
pub fn consume_ast(&self, ast: &AST) -> Result<(), Box<EvalAltResult>> { pub fn consume_ast(&self, ast: &AST) -> Result<(), Box<EvalAltResult>> {
self.consume_ast_with_scope(&mut Default::default(), ast) self.consume_ast_with_scope(&mut Default::default(), ast)
} }
/// Evaluate an `AST` with own scope, but throw away the result and only return error (if any). /// Evaluate an `AST` with own scope, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
#[inline(always)] #[inline(always)]
@ -1484,11 +1477,11 @@ impl Engine {
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
let mut mods = Default::default(); let mut mods = self.global_sub_modules.clone();
self.eval_statements_raw(scope, &mut mods, ast.statements(), &[ast.lib()]) self.eval_statements_raw(scope, &mut mods, ast.statements(), &[ast.lib()])
.map(|_| ()) .map(|_| ())
} }
/// Call a script function defined in an `AST` with multiple arguments. /// Call a script function defined in an `AST` with multiple arguments.
/// Arguments are passed as a tuple. /// Arguments are passed as a tuple.
/// ///
@ -1527,7 +1520,7 @@ impl Engine {
/// ``` /// ```
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[inline] #[inline]
pub fn call_fn<A: FuncArgs, T: Variant + Clone>( pub fn call_fn<A: crate::fn_args::FuncArgs, T: Variant + Clone>(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
@ -1535,7 +1528,7 @@ impl Engine {
args: A, args: A,
) -> Result<T, Box<EvalAltResult>> { ) -> Result<T, Box<EvalAltResult>> {
let mut arg_values = args.into_vec(); let mut arg_values = args.into_vec();
let mut args: StaticVec<_> = arg_values.as_mut().iter_mut().collect(); let mut args: crate::StaticVec<_> = arg_values.as_mut().iter_mut().collect();
let result = let result =
self.call_fn_dynamic_raw(scope, &[ast.lib()], name, &mut None, args.as_mut())?; self.call_fn_dynamic_raw(scope, &[ast.lib()], name, &mut None, args.as_mut())?;
@ -1551,7 +1544,6 @@ impl Engine {
.into() .into()
}); });
} }
/// Call a script function defined in an `AST` with multiple `Dynamic` arguments /// Call a script function defined in an `AST` with multiple `Dynamic` arguments
/// and optionally a value for binding to the 'this' pointer. /// and optionally a value for binding to the 'this' pointer.
/// ///
@ -1606,16 +1598,15 @@ impl Engine {
pub fn call_fn_dynamic( pub fn call_fn_dynamic(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
lib: impl AsRef<Module>, lib: impl AsRef<crate::Module>,
name: &str, name: &str,
mut this_ptr: Option<&mut Dynamic>, mut this_ptr: Option<&mut Dynamic>,
mut arg_values: impl AsMut<[Dynamic]>, mut arg_values: impl AsMut<[Dynamic]>,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
let mut args: StaticVec<_> = arg_values.as_mut().iter_mut().collect(); let mut args: crate::StaticVec<_> = arg_values.as_mut().iter_mut().collect();
self.call_fn_dynamic_raw(scope, &[lib.as_ref()], name, &mut this_ptr, args.as_mut()) self.call_fn_dynamic_raw(scope, &[lib.as_ref()], name, &mut this_ptr, args.as_mut())
} }
/// Call a script function defined in an `AST` with multiple `Dynamic` arguments. /// Call a script function defined in an `AST` with multiple `Dynamic` arguments.
/// ///
/// ## WARNING /// ## WARNING
@ -1629,7 +1620,7 @@ impl Engine {
pub(crate) fn call_fn_dynamic_raw( pub(crate) fn call_fn_dynamic_raw(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
lib: &[&Module], lib: &[&crate::Module],
name: &str, name: &str,
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
args: &mut FnCallArgs, args: &mut FnCallArgs,
@ -1640,16 +1631,15 @@ impl Engine {
.ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), NO_POS))?; .ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), NO_POS))?;
let mut state = Default::default(); let mut state = Default::default();
let mut mods = Default::default(); let mut mods = self.global_sub_modules.clone();
// Check for data race. // Check for data race.
if cfg!(not(feature = "no_closure")) { if cfg!(not(feature = "no_closure")) {
ensure_no_data_race(name, args, false)?; crate::fn_call::ensure_no_data_race(name, args, false)?;
} }
self.call_script_fn(scope, &mut mods, &mut state, lib, this_ptr, fn_def, args, 0) self.call_script_fn(scope, &mut mods, &mut state, lib, this_ptr, fn_def, args, 0)
} }
/// Optimize the `AST` with constants defined in an external Scope. /// Optimize the `AST` with constants defined in an external Scope.
/// An optimized copy of the `AST` is returned while the original `AST` is consumed. /// An optimized copy of the `AST` is returned while the original `AST` is consumed.
/// ///
@ -1680,10 +1670,9 @@ impl Engine {
#[cfg(feature = "no_function")] #[cfg(feature = "no_function")]
let lib = Default::default(); let lib = Default::default();
let stmt = mem::take(ast.statements_mut()); let stmt = crate::stdlib::mem::take(ast.statements_mut());
optimize_into_ast(self, scope, stmt, lib, optimization_level) crate::optimize::optimize_into_ast(self, scope, stmt, lib, optimization_level)
} }
/// Provide a callback that will be invoked before each variable access. /// Provide a callback that will be invoked before each variable access.
/// ///
/// ## Return Value of Callback /// ## Return Value of Callback
@ -1726,7 +1715,6 @@ impl Engine {
self.resolve_var = Some(Box::new(callback)); self.resolve_var = Some(Box::new(callback));
self self
} }
/// Register a callback for script evaluation progress. /// Register a callback for script evaluation progress.
/// ///
/// # Example /// # Example
@ -1769,7 +1757,6 @@ impl Engine {
self.progress = Some(Box::new(callback)); self.progress = Some(Box::new(callback));
self self
} }
/// Override default action of `print` (print to stdout using `println!`) /// Override default action of `print` (print to stdout using `println!`)
/// ///
/// # Example /// # Example
@ -1799,7 +1786,6 @@ impl Engine {
self.print = Box::new(callback); self.print = Box::new(callback);
self self
} }
/// Override default action of `debug` (print to stdout using `println!`) /// Override default action of `debug` (print to stdout using `println!`)
/// ///
/// # Example /// # Example

View File

@ -4,12 +4,6 @@ use crate::engine::Engine;
use crate::packages::PackageLibrary; use crate::packages::PackageLibrary;
use crate::token::{is_valid_identifier, Token}; use crate::token::{is_valid_identifier, Token};
#[cfg(not(feature = "no_module"))]
use crate::module::ModuleResolver;
#[cfg(not(feature = "no_optimize"))]
use crate::optimize::OptimizationLevel;
use crate::stdlib::{format, string::String}; use crate::stdlib::{format, string::String};
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -23,31 +17,30 @@ impl Engine {
/// In other words, loaded packages are searched in reverse order. /// In other words, loaded packages are searched in reverse order.
#[inline(always)] #[inline(always)]
pub fn load_package(&mut self, package: impl Into<PackageLibrary>) -> &mut Self { pub fn load_package(&mut self, package: impl Into<PackageLibrary>) -> &mut Self {
// Push the package to the top - packages are searched in reverse order self.packages.add(package.into());
self.packages.push(package.into());
self self
} }
/// 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. /// Not available under the `no_optimize` feature.
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
#[inline(always)] #[inline(always)]
pub fn set_optimization_level(&mut self, optimization_level: OptimizationLevel) -> &mut Self { pub fn set_optimization_level(
&mut self,
optimization_level: crate::OptimizationLevel,
) -> &mut Self {
self.optimization_level = optimization_level; self.optimization_level = optimization_level;
self self
} }
/// The current optimization level. /// The current optimization level.
/// It controls whether and how the `Engine` will optimize an AST after compilation. /// It controls whether and how the `Engine` will optimize an AST after compilation.
/// ///
/// Not available under the `no_optimize` feature. /// Not available under the `no_optimize` feature.
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
#[inline(always)] #[inline(always)]
pub fn optimization_level(&self) -> OptimizationLevel { pub fn optimization_level(&self) -> crate::OptimizationLevel {
self.optimization_level self.optimization_level
} }
/// Set the maximum levels of function calls allowed for a script in order to avoid /// Set the maximum levels of function calls allowed for a script in order to avoid
/// infinite recursion and stack overflows. /// infinite recursion and stack overflows.
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
@ -56,14 +49,12 @@ impl Engine {
self.limits.max_call_stack_depth = levels; self.limits.max_call_stack_depth = levels;
self self
} }
/// The maximum levels of function calls allowed for a script. /// The maximum levels of function calls allowed for a script.
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[inline(always)] #[inline(always)]
pub fn max_call_levels(&self) -> usize { pub fn max_call_levels(&self) -> usize {
self.limits.max_call_stack_depth self.limits.max_call_stack_depth
} }
/// Set the maximum number of operations allowed for a script to run to avoid /// Set the maximum number of operations allowed for a script to run to avoid
/// consuming too much resources (0 for unlimited). /// consuming too much resources (0 for unlimited).
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
@ -76,14 +67,12 @@ impl Engine {
}; };
self self
} }
/// The maximum number of operations allowed for a script to run (0 for unlimited). /// The maximum number of operations allowed for a script to run (0 for unlimited).
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[inline(always)] #[inline(always)]
pub fn max_operations(&self) -> u64 { pub fn max_operations(&self) -> u64 {
self.limits.max_operations self.limits.max_operations
} }
/// Set the maximum number of imported modules allowed for a script. /// Set the maximum number of imported modules allowed for a script.
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -92,7 +81,6 @@ impl Engine {
self.limits.max_modules = modules; self.limits.max_modules = modules;
self self
} }
/// The maximum number of imported modules allowed for a script. /// The maximum number of imported modules allowed for a script.
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -100,7 +88,6 @@ impl Engine {
pub fn max_modules(&self) -> usize { pub fn max_modules(&self) -> usize {
self.limits.max_modules self.limits.max_modules
} }
/// Set the depth limits for expressions (0 for unlimited). /// Set the depth limits for expressions (0 for unlimited).
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[inline(always)] #[inline(always)]
@ -124,14 +111,12 @@ impl Engine {
} }
self self
} }
/// The depth limit for expressions (0 for unlimited). /// The depth limit for expressions (0 for unlimited).
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[inline(always)] #[inline(always)]
pub fn max_expr_depth(&self) -> usize { pub fn max_expr_depth(&self) -> usize {
self.limits.max_expr_depth self.limits.max_expr_depth
} }
/// The depth limit for expressions in functions (0 for unlimited). /// The depth limit for expressions in functions (0 for unlimited).
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
@ -139,7 +124,6 @@ impl Engine {
pub fn max_function_expr_depth(&self) -> usize { pub fn max_function_expr_depth(&self) -> usize {
self.limits.max_function_expr_depth self.limits.max_function_expr_depth
} }
/// Set the maximum length of strings (0 for unlimited). /// Set the maximum length of strings (0 for unlimited).
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[inline(always)] #[inline(always)]
@ -147,14 +131,12 @@ impl Engine {
self.limits.max_string_size = if max_size == usize::MAX { 0 } else { max_size }; self.limits.max_string_size = if max_size == usize::MAX { 0 } else { max_size };
self self
} }
/// The maximum length of strings (0 for unlimited). /// The maximum length of strings (0 for unlimited).
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[inline(always)] #[inline(always)]
pub fn max_string_size(&self) -> usize { pub fn max_string_size(&self) -> usize {
self.limits.max_string_size self.limits.max_string_size
} }
/// Set the maximum length of arrays (0 for unlimited). /// Set the maximum length of arrays (0 for unlimited).
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -163,7 +145,6 @@ impl Engine {
self.limits.max_array_size = if max_size == usize::MAX { 0 } else { max_size }; self.limits.max_array_size = if max_size == usize::MAX { 0 } else { max_size };
self self
} }
/// The maximum length of arrays (0 for unlimited). /// The maximum length of arrays (0 for unlimited).
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -171,7 +152,6 @@ impl Engine {
pub fn max_array_size(&self) -> usize { pub fn max_array_size(&self) -> usize {
self.limits.max_array_size self.limits.max_array_size
} }
/// Set the maximum length of object maps (0 for unlimited). /// Set the maximum length of object maps (0 for unlimited).
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -180,7 +160,6 @@ impl Engine {
self.limits.max_map_size = if max_size == usize::MAX { 0 } else { max_size }; self.limits.max_map_size = if max_size == usize::MAX { 0 } else { max_size };
self self
} }
/// The maximum length of object maps (0 for unlimited). /// The maximum length of object maps (0 for unlimited).
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -188,7 +167,6 @@ impl Engine {
pub fn max_map_size(&self) -> usize { pub fn max_map_size(&self) -> usize {
self.limits.max_map_size self.limits.max_map_size
} }
/// Set the module resolution service used by the `Engine`. /// Set the module resolution service used by the `Engine`.
/// ///
/// Not available under the `no_module` feature. /// Not available under the `no_module` feature.
@ -196,12 +174,11 @@ impl Engine {
#[inline(always)] #[inline(always)]
pub fn set_module_resolver( pub fn set_module_resolver(
&mut self, &mut self,
resolver: Option<impl ModuleResolver + 'static>, resolver: Option<impl crate::ModuleResolver + 'static>,
) -> &mut Self { ) -> &mut Self {
self.module_resolver = resolver.map(|f| Box::new(f) as Box<dyn ModuleResolver>); self.module_resolver = resolver.map(|f| Box::new(f) as Box<dyn crate::ModuleResolver>);
self self
} }
/// Disable a particular keyword or operator in the language. /// Disable a particular keyword or operator in the language.
/// ///
/// # Examples /// # Examples
@ -243,7 +220,6 @@ impl Engine {
self.disabled_symbols.insert(symbol.into()); self.disabled_symbols.insert(symbol.into());
self self
} }
/// Register a custom operator into the language. /// Register a custom operator into the language.
/// ///
/// The operator must be a valid identifier (i.e. it cannot be a symbol). /// The operator must be a valid identifier (i.e. it cannot be a symbol).

View File

@ -12,29 +12,17 @@ use crate::module::{Module, NamespaceRef};
use crate::optimize::OptimizationLevel; use crate::optimize::OptimizationLevel;
use crate::parse_error::ParseErrorType; use crate::parse_error::ParseErrorType;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::Scope; use crate::scope::{EntryType as ScopeEntryType, Scope};
use crate::stdlib::ops::Deref; use crate::stdlib::ops::Deref;
use crate::token::NO_POS; use crate::token::NO_POS;
use crate::utils::ImmutableString; use crate::utils::ImmutableString;
use crate::{calc_native_fn_hash, calc_script_fn_hash, StaticVec, INT}; use crate::{calc_native_fn_hash, calc_script_fn_hash, StaticVec, INT};
#[cfg(not(feature = "no_function"))]
use crate::{
ast::ScriptFnDef, r#unsafe::unsafe_cast_var_name_to_lifetime,
scope::EntryType as ScopeEntryType,
};
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::FLOAT; use crate::FLOAT;
#[cfg(not(feature = "no_index"))]
use crate::engine::{FN_IDX_GET, FN_IDX_SET};
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::engine::{Map, Target, FN_GET, FN_SET}; use crate::Map;
#[cfg(not(feature = "no_closure"))]
use crate::engine::KEYWORD_IS_SHARED;
use crate::stdlib::{ use crate::stdlib::{
any::{type_name, TypeId}, any::{type_name, TypeId},
@ -47,9 +35,6 @@ use crate::stdlib::{
vec::Vec, vec::Vec,
}; };
#[cfg(not(feature = "no_function"))]
use crate::stdlib::borrow::Cow;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use num_traits::float::Float; use num_traits::float::Float;
@ -58,8 +43,8 @@ use num_traits::float::Float;
#[inline(always)] #[inline(always)]
fn extract_prop_from_getter(_fn_name: &str) -> Option<&str> { fn extract_prop_from_getter(_fn_name: &str) -> Option<&str> {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
if _fn_name.starts_with(FN_GET) { if _fn_name.starts_with(crate::engine::FN_GET) {
return Some(&_fn_name[FN_GET.len()..]); return Some(&_fn_name[crate::engine::FN_GET.len()..]);
} }
None None
@ -69,8 +54,8 @@ fn extract_prop_from_getter(_fn_name: &str) -> Option<&str> {
#[inline(always)] #[inline(always)]
fn extract_prop_from_setter(_fn_name: &str) -> Option<&str> { fn extract_prop_from_setter(_fn_name: &str) -> Option<&str> {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
if _fn_name.starts_with(FN_SET) { if _fn_name.starts_with(crate::engine::FN_SET) {
return Some(&_fn_name[FN_SET.len()..]); return Some(&_fn_name[crate::engine::FN_SET.len()..]);
} }
None None
@ -178,7 +163,7 @@ impl Engine {
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
pub(crate) fn call_native_fn( pub(crate) fn call_native_fn(
&self, &self,
mods: &mut Imports, mods: &Imports,
state: &mut State, state: &mut State,
lib: &[&Module], lib: &[&Module],
fn_name: &str, fn_name: &str,
@ -195,7 +180,8 @@ impl Engine {
// Then search packages // Then search packages
let func = //lib.get_fn(hash_fn, pub_only) let func = //lib.get_fn(hash_fn, pub_only)
self.global_module.get_fn(hash_fn, pub_only) self.global_module.get_fn(hash_fn, pub_only)
.or_else(|| self.packages.get_fn(hash_fn, pub_only)); .or_else(|| self.packages.get_fn(hash_fn))
.or_else(|| mods.get_fn(hash_fn));
if let Some(func) = func { if let Some(func) = func {
assert!(func.is_native()); assert!(func.is_native());
@ -286,7 +272,7 @@ impl Engine {
// index getter function not found? // index getter function not found?
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
if fn_name == FN_IDX_GET && args.len() == 2 { if fn_name == crate::engine::FN_IDX_GET && args.len() == 2 {
return EvalAltResult::ErrorFunctionNotFound( return EvalAltResult::ErrorFunctionNotFound(
format!( format!(
"{} [{}]", "{} [{}]",
@ -300,7 +286,7 @@ impl Engine {
// index setter function not found? // index setter function not found?
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
if fn_name == FN_IDX_SET { if fn_name == crate::engine::FN_IDX_SET {
return EvalAltResult::ErrorFunctionNotFound( return EvalAltResult::ErrorFunctionNotFound(
format!( format!(
"{} [{}]=", "{} [{}]=",
@ -347,7 +333,7 @@ impl Engine {
state: &mut State, state: &mut State,
lib: &[&Module], lib: &[&Module],
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
fn_def: &ScriptFnDef, fn_def: &crate::ast::ScriptFnDef,
args: &mut FnCallArgs, args: &mut FnCallArgs,
level: usize, level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
@ -374,7 +360,8 @@ impl Engine {
.iter() .iter()
.zip(args.iter_mut().map(|v| mem::take(*v))) .zip(args.iter_mut().map(|v| mem::take(*v)))
.map(|(name, value)| { .map(|(name, value)| {
let var_name: Cow<'_, str> = unsafe_cast_var_name_to_lifetime(name).into(); let var_name: crate::stdlib::borrow::Cow<'_, str> =
crate::r#unsafe::unsafe_cast_var_name_to_lifetime(name).into();
(var_name, ScopeEntryType::Normal, value) (var_name, ScopeEntryType::Normal, value)
}), }),
); );
@ -431,6 +418,7 @@ impl Engine {
#[inline] #[inline]
pub(crate) fn has_override_by_name_and_arguments( pub(crate) fn has_override_by_name_and_arguments(
&self, &self,
mods: &Imports,
lib: &[&Module], lib: &[&Module],
fn_name: &str, fn_name: &str,
arg_types: impl AsRef<[TypeId]>, arg_types: impl AsRef<[TypeId]>,
@ -440,13 +428,14 @@ impl Engine {
let hash_fn = calc_native_fn_hash(empty(), fn_name, arg_types.iter().cloned()); let hash_fn = calc_native_fn_hash(empty(), fn_name, arg_types.iter().cloned());
let hash_script = calc_script_fn_hash(empty(), fn_name, arg_types.len()); let hash_script = calc_script_fn_hash(empty(), fn_name, arg_types.len());
self.has_override(lib, hash_fn, hash_script, pub_only) self.has_override(mods, lib, hash_fn, hash_script, pub_only)
} }
// Has a system function an override? // Has a system function an override?
#[inline(always)] #[inline(always)]
pub(crate) fn has_override( pub(crate) fn has_override(
&self, &self,
mods: &Imports,
lib: &[&Module], lib: &[&Module],
hash_fn: u64, hash_fn: u64,
hash_script: u64, hash_script: u64,
@ -459,10 +448,13 @@ impl Engine {
//|| lib.iter().any(|&m| m.contains_fn(hash_fn, pub_only)) //|| lib.iter().any(|&m| m.contains_fn(hash_fn, pub_only))
// Then check registered functions // Then check registered functions
//|| self.global_module.contains_fn(hash_script, pub_only) //|| self.global_module.contains_fn(hash_script, pub_only)
|| self.global_module.contains_fn(hash_fn, pub_only) || self.global_module.contains_fn(hash_fn, false)
// Then check packages // Then check packages
|| self.packages.contains_fn(hash_script, pub_only) || self.packages.contains_fn(hash_script)
|| self.packages.contains_fn(hash_fn, pub_only) || self.packages.contains_fn(hash_fn)
// Then check imported modules
|| mods.contains_fn(hash_script)
|| mods.contains_fn(hash_fn)
} }
/// Perform an actual function call, native Rust or scripted, taking care of special functions. /// Perform an actual function call, native Rust or scripted, taking care of special functions.
@ -500,7 +492,8 @@ impl Engine {
match fn_name { match fn_name {
// type_of // type_of
KEYWORD_TYPE_OF KEYWORD_TYPE_OF
if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) => if args.len() == 1
&& !self.has_override(mods, lib, hash_fn, hash_script, pub_only) =>
{ {
Ok(( Ok((
self.map_type_name(args[0].type_name()).to_string().into(), self.map_type_name(args[0].type_name()).to_string().into(),
@ -511,7 +504,8 @@ impl Engine {
// Fn/eval - reaching this point it must be a method-style call, mostly like redirected // Fn/eval - reaching this point it must be a method-style call, mostly like redirected
// by a function pointer so it isn't caught at parse time. // by a function pointer so it isn't caught at parse time.
KEYWORD_FN_PTR | KEYWORD_EVAL KEYWORD_FN_PTR | KEYWORD_EVAL
if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) => if args.len() == 1
&& !self.has_override(mods, lib, hash_fn, hash_script, pub_only) =>
{ {
EvalAltResult::ErrorRuntime( EvalAltResult::ErrorRuntime(
format!( format!(
@ -526,16 +520,14 @@ impl Engine {
// Script-like function found // Script-like function found
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
_ if lib.iter().any(|&m| m.contains_fn(hash_script, pub_only)) _ if self.has_override(mods, lib, 0, hash_script, pub_only) => {
//|| self.global_module.contains_fn(hash_script, pub_only)
|| self.packages.contains_fn(hash_script, pub_only) =>
{
// Get function // Get function
let func = lib let func = lib
.iter() .iter()
.find_map(|&m| m.get_fn(hash_script, pub_only)) .find_map(|&m| m.get_fn(hash_script, pub_only))
//.or_else(|| self.global_module.get_fn(hash_script, pub_only)) //.or_else(|| self.global_module.get_fn(hash_script, pub_only))
.or_else(|| self.packages.get_fn(hash_script, pub_only)) .or_else(|| self.packages.get_fn(hash_script))
//.or_else(|| mods.get_fn(hash_script))
.unwrap(); .unwrap();
if func.is_script() { if func.is_script() {
@ -654,7 +646,7 @@ impl Engine {
let script = script.trim(); let script = script.trim();
if script.is_empty() { if script.is_empty() {
return Ok(().into()); return Ok(Dynamic::UNIT);
} }
// Check for stack overflow // Check for stack overflow
@ -696,7 +688,7 @@ impl Engine {
lib: &[&Module], lib: &[&Module],
fn_name: &str, fn_name: &str,
hash_script: u64, hash_script: u64,
target: &mut Target, target: &mut crate::engine::Target,
mut call_args: StaticVec<Dynamic>, mut call_args: StaticVec<Dynamic>,
def_val: Option<Dynamic>, def_val: Option<Dynamic>,
native: bool, native: bool,
@ -779,7 +771,7 @@ impl Engine {
} else if { } else if {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
{ {
fn_name == KEYWORD_IS_SHARED && call_args.is_empty() fn_name == crate::engine::KEYWORD_IS_SHARED && call_args.is_empty()
} }
#[cfg(feature = "no_closure")] #[cfg(feature = "no_closure")]
false false
@ -863,7 +855,7 @@ impl Engine {
let hash_fn = let hash_fn =
calc_native_fn_hash(empty(), fn_name, once(TypeId::of::<ImmutableString>())); calc_native_fn_hash(empty(), fn_name, once(TypeId::of::<ImmutableString>()));
if !self.has_override(lib, hash_fn, hash_script, pub_only) { if !self.has_override(mods, lib, hash_fn, hash_script, pub_only) {
// Fn - only in function call style // Fn - only in function call style
return self return self
.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)? .eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?
@ -905,7 +897,7 @@ impl Engine {
// Handle is_shared() // Handle is_shared()
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
if fn_name == KEYWORD_IS_SHARED && args_expr.len() == 1 { if fn_name == crate::engine::KEYWORD_IS_SHARED && args_expr.len() == 1 {
let value = self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?; let value = self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?;
return Ok(value.is_shared().into()); return Ok(value.is_shared().into());
@ -919,7 +911,7 @@ impl Engine {
if name == KEYWORD_FN_PTR_CALL if name == KEYWORD_FN_PTR_CALL
&& args_expr.len() >= 1 && args_expr.len() >= 1
&& !self.has_override(lib, 0, hash_script, pub_only) && !self.has_override(mods, lib, 0, hash_script, pub_only)
{ {
let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?; let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?;
@ -949,7 +941,7 @@ impl Engine {
if name == KEYWORD_IS_DEF_VAR && args_expr.len() == 1 { if name == KEYWORD_IS_DEF_VAR && args_expr.len() == 1 {
let hash_fn = calc_native_fn_hash(empty(), name, once(TypeId::of::<ImmutableString>())); let hash_fn = calc_native_fn_hash(empty(), name, once(TypeId::of::<ImmutableString>()));
if !self.has_override(lib, hash_fn, hash_script, pub_only) { if !self.has_override(mods, lib, hash_fn, hash_script, pub_only) {
let var_name = let var_name =
self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?; self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?;
let var_name = var_name.as_str().map_err(|err| { let var_name = var_name.as_str().map_err(|err| {
@ -969,7 +961,7 @@ impl Engine {
.cloned(), .cloned(),
); );
if !self.has_override(lib, hash_fn, hash_script, pub_only) { if !self.has_override(mods, lib, hash_fn, hash_script, pub_only) {
let fn_name = let fn_name =
self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?; self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?;
let num_params = let num_params =
@ -996,7 +988,7 @@ impl Engine {
if name == KEYWORD_EVAL && args_expr.len() == 1 { if name == KEYWORD_EVAL && args_expr.len() == 1 {
let hash_fn = calc_native_fn_hash(empty(), name, once(TypeId::of::<ImmutableString>())); let hash_fn = calc_native_fn_hash(empty(), name, once(TypeId::of::<ImmutableString>()));
if !self.has_override(lib, hash_fn, hash_script, pub_only) { if !self.has_override(mods, lib, hash_fn, hash_script, pub_only) {
// eval - only in function call style // eval - only in function call style
let prev_len = scope.len(); let prev_len = scope.len();
let script = let script =
@ -1042,9 +1034,14 @@ impl Engine {
.map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level))
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
let (target, _, _, pos) = let (target, _, typ, pos) =
self.search_namespace(scope, mods, state, lib, this_ptr, &args_expr[0])?; self.search_namespace(scope, mods, state, lib, this_ptr, &args_expr[0])?;
let target = match typ {
ScopeEntryType::Normal => target,
ScopeEntryType::Constant => target.into_owned(),
};
self.inc_operations(state) self.inc_operations(state)
.map_err(|err| err.fill_position(pos))?; .map_err(|err| err.fill_position(pos))?;
@ -1192,7 +1189,7 @@ impl Engine {
Some(f) if f.is_plugin_fn() => f Some(f) if f.is_plugin_fn() => f
.get_plugin_fn() .get_plugin_fn()
.clone() .clone()
.call((self, mods, lib).into(), args.as_mut()), .call((self, &*mods, lib).into(), args.as_mut()),
Some(f) if f.is_native() => { Some(f) if f.is_native() => {
if !f.is_method() { if !f.is_method() {
// Clone first argument // Clone first argument
@ -1203,7 +1200,7 @@ impl Engine {
} }
} }
f.get_native_fn().clone()((self, mods, lib).into(), args.as_mut()) f.get_native_fn().clone()((self, &*mods, lib).into(), args.as_mut())
} }
Some(_) => unreachable!(), Some(_) => unreachable!(),
None if def_val.is_some() => Ok(def_val.unwrap().into()), None if def_val.is_some() => Ok(def_val.unwrap().into()),

View File

@ -10,15 +10,19 @@ use crate::token::{is_valid_identifier, NO_POS};
use crate::utils::ImmutableString; use crate::utils::ImmutableString;
use crate::{calc_script_fn_hash, StaticVec}; use crate::{calc_script_fn_hash, StaticVec};
#[cfg(not(feature = "no_function"))]
use crate::engine::FN_ANONYMOUS;
use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, iter::empty, mem, string::String}; use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, iter::empty, mem, string::String};
#[cfg(feature = "sync")]
use crate::stdlib::sync::{Arc, RwLock};
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
use crate::stdlib::{cell::RefCell, rc::Rc}; use crate::stdlib::rc::Rc;
#[cfg(feature = "sync")]
use crate::stdlib::sync::Arc;
#[cfg(any(not(feature = "no_closure"), not(feature = "no_module")))]
#[cfg(not(feature = "sync"))]
use crate::stdlib::cell::RefCell;
#[cfg(any(not(feature = "no_closure"), not(feature = "no_module")))]
#[cfg(feature = "sync")]
use crate::stdlib::sync::RwLock;
/// Trait that maps to `Send + Sync` only under the `sync` feature. /// Trait that maps to `Send + Sync` only under the `sync` feature.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
@ -42,9 +46,11 @@ pub type Shared<T> = Rc<T>;
pub type Shared<T> = Arc<T>; pub type Shared<T> = Arc<T>;
/// Synchronized shared object. /// Synchronized shared object.
#[cfg(any(not(feature = "no_closure"), not(feature = "no_module")))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type Locked<T> = RefCell<T>; pub type Locked<T> = RefCell<T>;
/// Synchronized shared object. /// Synchronized shared object.
#[cfg(any(not(feature = "no_closure"), not(feature = "no_module")))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type Locked<T> = RwLock<T>; pub type Locked<T> = RwLock<T>;
@ -56,10 +62,10 @@ pub struct NativeCallContext<'e, 'a, 'm, 'pm: 'm> {
lib: &'m [&'pm Module], lib: &'m [&'pm Module],
} }
impl<'e, 'a, 'm, 'pm: 'm, M: AsRef<[&'pm Module]> + ?Sized> impl<'e, 'a, 'm, 'pm: 'm, M: AsRef<[&'pm Module]> + ?Sized> From<(&'e Engine, &'a Imports, &'m M)>
From<(&'e Engine, &'a mut Imports, &'m M)> for NativeCallContext<'e, 'a, 'm, 'pm> for NativeCallContext<'e, 'a, 'm, 'pm>
{ {
fn from(value: (&'e Engine, &'a mut Imports, &'m M)) -> Self { fn from(value: (&'e Engine, &'a Imports, &'m M)) -> Self {
Self { Self {
engine: value.0, engine: value.0,
mods: Some(value.1), mods: Some(value.1),
@ -249,7 +255,7 @@ impl FnPtr {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[inline(always)] #[inline(always)]
pub fn is_anonymous(&self) -> bool { pub fn is_anonymous(&self) -> bool {
self.0.starts_with(FN_ANONYMOUS) self.0.starts_with(crate::engine::FN_ANONYMOUS)
} }
/// Call the function pointer with curried arguments (if any). /// Call the function pointer with curried arguments (if any).
/// ///

View File

@ -124,6 +124,9 @@ pub use syntax::Expression;
pub use token::{Position, NO_POS}; pub use token::{Position, NO_POS};
pub use utils::ImmutableString; pub use utils::ImmutableString;
#[allow(dead_code)]
use fn_native::{Locked, Shared};
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
pub use utils::{calc_native_fn_hash, calc_script_fn_hash}; pub use utils::{calc_native_fn_hash, calc_script_fn_hash};

View File

@ -10,23 +10,14 @@ use crate::fn_register::by_value as cast_arg;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::token::{Token, NO_POS}; use crate::token::{Token, NO_POS};
use crate::utils::{ImmutableString, StraightHasherBuilder}; use crate::utils::{ImmutableString, StraightHasherBuilder};
use crate::{calc_native_fn_hash, calc_script_fn_hash, StaticVec}; use crate::StaticVec;
#[cfg(not(feature = "no_function"))]
use crate::ast::ScriptFnDef;
#[cfg(not(feature = "no_module"))]
use crate::{ast::AST, engine::Engine, scope::Scope};
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::engine::{Array, FN_IDX_GET, FN_IDX_SET}; use crate::Array;
#[cfg(not(feature = "no_object"))]
use crate::engine::{make_getter, make_setter};
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::engine::Map; use crate::Map;
use crate::stdlib::{ use crate::stdlib::{
any::TypeId, any::TypeId,
@ -59,7 +50,7 @@ pub struct FuncInfo {
/// and/or script-defined functions. /// and/or script-defined functions.
/// ///
/// Not available under the `no_module` feature. /// Not available under the `no_module` feature.
#[derive(Default, Clone)] #[derive(Clone)]
pub struct Module { pub struct Module {
/// Sub-modules. /// Sub-modules.
modules: HashMap<String, Shared<Module>>, modules: HashMap<String, Shared<Module>>,
@ -69,15 +60,32 @@ pub struct Module {
all_variables: HashMap<u64, Dynamic, StraightHasherBuilder>, all_variables: HashMap<u64, Dynamic, StraightHasherBuilder>,
/// External Rust functions. /// External Rust functions.
functions: HashMap<u64, FuncInfo, StraightHasherBuilder>, functions: HashMap<u64, FuncInfo, StraightHasherBuilder>,
/// Iterator functions, keyed by the type producing the iterator. /// Flattened collection of all external Rust functions, native or scripted.
type_iterators: HashMap<TypeId, IteratorFn>,
/// Flattened collection of all external Rust functions, native or scripted,
/// including those in sub-modules. /// including those in sub-modules.
all_functions: HashMap<u64, CallableFunction, StraightHasherBuilder>, all_functions: HashMap<u64, CallableFunction, StraightHasherBuilder>,
/// Iterator functions, keyed by the type producing the iterator.
type_iterators: HashMap<TypeId, IteratorFn>,
/// Flattened collection of iterator functions, including those in sub-modules.
all_type_iterators: HashMap<TypeId, IteratorFn>,
/// Is the module indexed? /// Is the module indexed?
indexed: bool, indexed: bool,
} }
impl Default for Module {
fn default() -> Self {
Self {
modules: Default::default(),
variables: Default::default(),
all_variables: Default::default(),
functions: HashMap::with_capacity_and_hasher(64, StraightHasherBuilder),
all_functions: HashMap::with_capacity_and_hasher(256, StraightHasherBuilder),
type_iterators: Default::default(),
all_type_iterators: Default::default(),
indexed: false,
}
}
}
impl fmt::Debug for Module { impl fmt::Debug for Module {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!( write!(
@ -163,6 +171,7 @@ impl Module {
&& self.all_variables.is_empty() && self.all_variables.is_empty()
&& self.modules.is_empty() && self.modules.is_empty()
&& self.type_iterators.is_empty() && self.type_iterators.is_empty()
&& self.all_type_iterators.is_empty()
} }
/// Is the module indexed? /// Is the module indexed?
@ -273,10 +282,10 @@ impl Module {
/// If there is an existing function of the same name and number of arguments, it is replaced. /// If there is an existing function of the same name and number of arguments, it is replaced.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[inline] #[inline]
pub(crate) fn set_script_fn(&mut self, fn_def: Shared<ScriptFnDef>) -> u64 { pub(crate) fn set_script_fn(&mut self, fn_def: Shared<crate::ast::ScriptFnDef>) -> u64 {
// None + function name + number of arguments. // None + function name + number of arguments.
let num_params = fn_def.params.len(); let num_params = fn_def.params.len();
let hash_script = calc_script_fn_hash(empty(), &fn_def.name, num_params); let hash_script = crate::calc_script_fn_hash(empty(), &fn_def.name, num_params);
self.functions.insert( self.functions.insert(
hash_script, hash_script,
FuncInfo { FuncInfo {
@ -299,7 +308,7 @@ impl Module {
name: &str, name: &str,
num_params: usize, num_params: usize,
public_only: bool, public_only: bool,
) -> Option<&Shared<ScriptFnDef>> { ) -> Option<&Shared<crate::ast::ScriptFnDef>> {
self.functions self.functions
.values() .values()
.find( .find(
@ -437,7 +446,7 @@ impl Module {
) -> u64 { ) -> u64 {
let name = name.into(); let name = name.into();
let hash_fn = calc_native_fn_hash(empty(), &name, arg_types.iter().cloned()); let hash_fn = crate::calc_native_fn_hash(empty(), &name, arg_types.iter().cloned());
let params = arg_types let params = arg_types
.into_iter() .into_iter()
@ -656,7 +665,7 @@ impl Module {
name: impl Into<String>, name: impl Into<String>,
func: impl Fn(&mut A) -> Result<T, Box<EvalAltResult>> + SendSync + 'static, func: impl Fn(&mut A) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
) -> u64 { ) -> u64 {
self.set_fn_1_mut(make_getter(&name.into()), func) self.set_fn_1_mut(crate::engine::make_getter(&name.into()), func)
} }
/// Set a Rust function taking two parameters into the module, returning a hash key. /// Set a Rust function taking two parameters into the module, returning a hash key.
@ -756,7 +765,7 @@ impl Module {
name: impl Into<String>, name: impl Into<String>,
func: impl Fn(&mut A, B) -> Result<(), Box<EvalAltResult>> + SendSync + 'static, func: impl Fn(&mut A, B) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
) -> u64 { ) -> u64 {
self.set_fn_2_mut(make_setter(&name.into()), func) self.set_fn_2_mut(crate::engine::make_setter(&name.into()), func)
} }
/// Set a Rust index getter taking two parameters (the first one mutable) into the module, /// Set a Rust index getter taking two parameters (the first one mutable) into the module,
@ -800,7 +809,7 @@ impl Module {
panic!("Cannot register indexer for strings."); panic!("Cannot register indexer for strings.");
} }
self.set_fn_2_mut(FN_IDX_GET, func) self.set_fn_2_mut(crate::engine::FN_IDX_GET, func)
} }
/// Set a Rust function taking three parameters into the module, returning a hash key. /// Set a Rust function taking three parameters into the module, returning a hash key.
@ -939,7 +948,7 @@ impl Module {
}; };
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()]; let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn( self.set_fn(
FN_IDX_SET, crate::engine::FN_IDX_SET,
FnAccess::Public, FnAccess::Public,
&arg_types, &arg_types,
CallableFunction::from_method(Box::new(f)), CallableFunction::from_method(Box::new(f)),
@ -1105,6 +1114,15 @@ impl Module {
} }
} }
/// Does the particular namespace-qualified function exist in the module?
///
/// The `u64` hash is calculated by the function `crate::calc_native_fn_hash` and must match
/// the hash calculated by `build_index`.
#[inline]
pub fn contains_qualified_fn(&self, hash_fn: u64) -> bool {
self.all_functions.contains_key(&hash_fn)
}
/// Get a namespace-qualified function. /// Get a namespace-qualified function.
/// Name and Position in `EvalAltResult` are None and must be set afterwards. /// Name and Position in `EvalAltResult` are None and must be set afterwards.
/// ///
@ -1125,6 +1143,7 @@ impl Module {
self.type_iterators.extend(other.type_iterators.into_iter()); self.type_iterators.extend(other.type_iterators.into_iter());
self.all_functions.clear(); self.all_functions.clear();
self.all_variables.clear(); self.all_variables.clear();
self.all_type_iterators.clear();
self.indexed = false; self.indexed = false;
self self
} }
@ -1142,6 +1161,7 @@ impl Module {
self.type_iterators.extend(other.type_iterators.into_iter()); self.type_iterators.extend(other.type_iterators.into_iter());
self.all_functions.clear(); self.all_functions.clear();
self.all_variables.clear(); self.all_variables.clear();
self.all_type_iterators.clear();
self.indexed = false; self.indexed = false;
self self
} }
@ -1168,6 +1188,7 @@ impl Module {
}); });
self.all_functions.clear(); self.all_functions.clear();
self.all_variables.clear(); self.all_variables.clear();
self.all_type_iterators.clear();
self.indexed = false; self.indexed = false;
self self
} }
@ -1213,6 +1234,7 @@ impl Module {
self.type_iterators.extend(other.type_iterators.iter()); self.type_iterators.extend(other.type_iterators.iter());
self.all_functions.clear(); self.all_functions.clear();
self.all_variables.clear(); self.all_variables.clear();
self.all_type_iterators.clear();
self.indexed = false; self.indexed = false;
self self
} }
@ -1232,6 +1254,7 @@ impl Module {
self.all_functions.clear(); self.all_functions.clear();
self.all_variables.clear(); self.all_variables.clear();
self.all_type_iterators.clear();
self.indexed = false; self.indexed = false;
self self
} }
@ -1271,7 +1294,7 @@ impl Module {
#[inline(always)] #[inline(always)]
pub(crate) fn iter_script_fn<'a>( pub(crate) fn iter_script_fn<'a>(
&'a self, &'a self,
) -> impl Iterator<Item = (FnAccess, &str, usize, Shared<ScriptFnDef>)> + 'a { ) -> impl Iterator<Item = (FnAccess, &str, usize, Shared<crate::ast::ScriptFnDef>)> + 'a {
self.functions self.functions
.values() .values()
.map(|f| &f.func) .map(|f| &f.func)
@ -1316,7 +1339,7 @@ impl Module {
#[inline(always)] #[inline(always)]
pub fn iter_script_fn_info( pub fn iter_script_fn_info(
&self, &self,
) -> impl Iterator<Item = (FnAccess, &str, usize, Shared<ScriptFnDef>)> { ) -> impl Iterator<Item = (FnAccess, &str, usize, Shared<crate::ast::ScriptFnDef>)> {
self.iter_script_fn() self.iter_script_fn()
} }
@ -1343,11 +1366,12 @@ impl Module {
/// ``` /// ```
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub fn eval_ast_as_new( pub fn eval_ast_as_new(
mut scope: Scope, mut scope: crate::Scope,
ast: &AST, ast: &crate::AST,
engine: &Engine, engine: &crate::Engine,
) -> Result<Self, Box<EvalAltResult>> { ) -> Result<Self, Box<EvalAltResult>> {
let mut mods = Default::default(); let mut mods = engine.global_sub_modules.clone();
let orig_mods_len = mods.len();
// Run the script // Run the script
engine.eval_ast_with_scope_raw(&mut scope, &mut mods, &ast)?; engine.eval_ast_with_scope_raw(&mut scope, &mut mods, &ast)?;
@ -1366,8 +1390,14 @@ impl Module {
} }
}); });
// Modules left in the scope become sub-modules // Extra modules left in the scope become sub-modules
mods.iter().for_each(|(alias, m)| { let mut func_mods: crate::engine::Imports = Default::default();
mods.into_iter()
.skip(orig_mods_len)
.filter(|&(_, fixed, _)| !fixed)
.for_each(|(alias, _, m)| {
func_mods.push(alias.clone(), m.clone());
module.set_sub_module(alias, m); module.set_sub_module(alias, m);
}); });
@ -1382,7 +1412,7 @@ impl Module {
// Encapsulate AST environment // Encapsulate AST environment
let mut func = func.as_ref().clone(); let mut func = func.as_ref().clone();
func.lib = Some(ast_lib.clone()); func.lib = Some(ast_lib.clone());
func.mods = mods.clone(); func.mods = func_mods.clone();
module.set_script_fn(func.into()); module.set_script_fn(func.into());
}); });
} }
@ -1400,22 +1430,30 @@ impl Module {
fn index_module<'a>( fn index_module<'a>(
module: &'a Module, module: &'a Module,
qualifiers: &mut Vec<&'a str>, qualifiers: &mut Vec<&'a str>,
variables: &mut Vec<(u64, Dynamic)>, variables: &mut HashMap<u64, Dynamic, StraightHasherBuilder>,
functions: &mut Vec<(u64, CallableFunction)>, functions: &mut HashMap<u64, CallableFunction, StraightHasherBuilder>,
type_iterators: &mut HashMap<TypeId, IteratorFn>,
) { ) {
module.modules.iter().for_each(|(name, m)| { module.modules.iter().for_each(|(name, m)| {
// Index all the sub-modules first. // Index all the sub-modules first.
qualifiers.push(name); qualifiers.push(name);
index_module(m, qualifiers, variables, functions); index_module(m, qualifiers, variables, functions, type_iterators);
qualifiers.pop(); qualifiers.pop();
}); });
// Index all variables // Index all variables
module.variables.iter().for_each(|(var_name, value)| { module.variables.iter().for_each(|(var_name, value)| {
// Qualifiers + variable name // Qualifiers + variable name
let hash_var = calc_script_fn_hash(qualifiers.iter().map(|&v| v), var_name, 0); let hash_var =
variables.push((hash_var, value.clone())); crate::calc_script_fn_hash(qualifiers.iter().map(|&v| v), var_name, 0);
variables.insert(hash_var, value.clone());
}); });
// Index type iterators
module.type_iterators.iter().for_each(|(&type_id, func)| {
type_iterators.insert(type_id, func.clone());
});
// Index all Rust functions // Index all Rust functions
module module
.functions .functions
@ -1423,7 +1461,7 @@ impl Module {
.filter(|(_, FuncInfo { access, .. })| access.is_public()) .filter(|(_, FuncInfo { access, .. })| access.is_public())
.for_each( .for_each(
|( |(
&_hash, &hash,
FuncInfo { FuncInfo {
name, name,
params, params,
@ -1432,50 +1470,80 @@ impl Module {
.. ..
}, },
)| { )| {
// Flatten all methods so they can be available without namespace qualifiers
#[cfg(not(feature = "no_object"))]
if func.is_method() {
functions.insert(hash, func.clone());
}
if let Some(param_types) = types { if let Some(param_types) = types {
assert_eq!(*params, param_types.len()); assert_eq!(*params, param_types.len());
// Namespace-qualified Rust functions are indexed in two steps: // Namespace-qualified Rust functions are indexed in two steps:
// 1) Calculate a hash in a similar manner to script-defined functions, // 1) Calculate a hash in a similar manner to script-defined functions,
// i.e. qualifiers + function name + number of arguments. // i.e. qualifiers + function name + number of arguments.
let hash_qualified_script = let hash_qualified_script = crate::calc_script_fn_hash(
calc_script_fn_hash(qualifiers.iter().cloned(), name, *params); qualifiers.iter().cloned(),
name,
*params,
);
// 2) Calculate a second hash with no qualifiers, empty function name, // 2) Calculate a second hash with no qualifiers, empty function name,
// and the actual list of argument `TypeId`'.s // and the actual list of argument `TypeId`'.s
let hash_fn_args = let hash_fn_args = crate::calc_native_fn_hash(
calc_native_fn_hash(empty(), "", param_types.iter().cloned()); empty(),
"",
param_types.iter().cloned(),
);
// 3) The final hash is the XOR of the two hashes. // 3) The final hash is the XOR of the two hashes.
let hash_qualified_fn = hash_qualified_script ^ hash_fn_args; let hash_qualified_fn = hash_qualified_script ^ hash_fn_args;
functions.push((hash_qualified_fn, func.clone())); functions.insert(hash_qualified_fn, func.clone());
} else if cfg!(not(feature = "no_function")) { } else if cfg!(not(feature = "no_function")) {
let hash_qualified_script = if qualifiers.is_empty() { let hash_qualified_script =
_hash if cfg!(feature = "no_object") && qualifiers.is_empty() {
hash
} else { } else {
// Qualifiers + function name + number of arguments. // Qualifiers + function name + number of arguments.
calc_script_fn_hash(qualifiers.iter().map(|&v| v), &name, *params) crate::calc_script_fn_hash(
qualifiers.iter().map(|&v| v),
&name,
*params,
)
}; };
functions.push((hash_qualified_script, func.clone())); functions.insert(hash_qualified_script, func.clone());
} }
}, },
); );
} }
if !self.indexed { if !self.indexed {
let mut qualifiers: Vec<_> = Default::default(); let mut qualifiers = Vec::with_capacity(4);
let mut variables: Vec<_> = Default::default(); let mut variables = HashMap::with_capacity_and_hasher(16, StraightHasherBuilder);
let mut functions: Vec<_> = Default::default(); let mut functions = HashMap::with_capacity_and_hasher(256, StraightHasherBuilder);
let mut type_iterators = HashMap::with_capacity(16);
qualifiers.push("root"); qualifiers.push("root");
index_module(self, &mut qualifiers, &mut variables, &mut functions); index_module(
self,
&mut qualifiers,
&mut variables,
&mut functions,
&mut type_iterators,
);
self.all_variables = variables.into_iter().collect(); self.all_variables = variables;
self.all_functions = functions.into_iter().collect(); self.all_functions = functions;
self.all_type_iterators = type_iterators;
self.indexed = true; self.indexed = true;
} }
} }
/// Does a type iterator exist in the entire module tree?
pub fn contains_qualified_iter(&self, id: TypeId) -> bool {
self.all_type_iterators.contains_key(&id)
}
/// Does a type iterator exist in the module? /// Does a type iterator exist in the module?
pub fn contains_iter(&self, id: TypeId) -> bool { pub fn contains_iter(&self, id: TypeId) -> bool {
self.type_iterators.contains_key(&id) self.type_iterators.contains_key(&id)
@ -1510,6 +1578,11 @@ impl Module {
}) })
} }
/// Get the specified type iterator.
pub(crate) fn get_qualified_iter(&self, id: TypeId) -> Option<IteratorFn> {
self.all_type_iterators.get(&id).cloned()
}
/// Get the specified type iterator. /// Get the specified type iterator.
pub(crate) fn get_iter(&self, id: TypeId) -> Option<IteratorFn> { pub(crate) fn get_iter(&self, id: TypeId) -> Option<IteratorFn> {
self.type_iterators.get(&id).cloned() self.type_iterators.get(&id).cloned()

View File

@ -14,9 +14,6 @@ use crate::token::{is_valid_identifier, Position, NO_POS};
use crate::utils::get_hasher; use crate::utils::get_hasher;
use crate::{calc_native_fn_hash, StaticVec}; use crate::{calc_native_fn_hash, StaticVec};
#[cfg(not(feature = "no_function"))]
use crate::ast::ReturnType;
use crate::stdlib::{ use crate::stdlib::{
boxed::Box, boxed::Box,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
@ -237,7 +234,7 @@ fn optimize_stmt_block(
} }
match stmt { match stmt {
Stmt::ReturnWithVal(_, _, _) | Stmt::Break(_) => dead_code = true, Stmt::Return(_, _, _) | Stmt::Break(_) => dead_code = true,
_ => (), _ => (),
} }
@ -285,20 +282,19 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
optimize_expr(&mut x.2, state); optimize_expr(&mut x.2, state);
} }
}, },
// if false { if_block } -> Noop // if false { if_block } -> Noop
Stmt::IfThenElse(Expr::False(pos), x, _) if x.1.is_none() => { Stmt::If(Expr::False(pos), x, _) if x.1.is_none() => {
state.set_dirty(); state.set_dirty();
*stmt = Stmt::Noop(*pos); *stmt = Stmt::Noop(*pos);
} }
// if true { if_block } -> if_block // if true { if_block } -> if_block
Stmt::IfThenElse(Expr::True(_), x, _) if x.1.is_none() => { Stmt::If(Expr::True(_), x, _) if x.1.is_none() => {
*stmt = mem::take(&mut x.0); *stmt = mem::take(&mut x.0);
optimize_stmt(stmt, state, true); optimize_stmt(stmt, state, true);
} }
// if expr { Noop } // if expr { Noop }
Stmt::IfThenElse(ref mut condition, x, _) Stmt::If(ref mut condition, x, _) if x.1.is_none() && matches!(x.0, Stmt::Noop(_)) => {
if x.1.is_none() && matches!(x.0, Stmt::Noop(_)) =>
{
state.set_dirty(); state.set_dirty();
let pos = condition.position(); let pos = condition.position();
@ -317,22 +313,22 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
}; };
} }
// if expr { if_block } // if expr { if_block }
Stmt::IfThenElse(ref mut condition, ref mut x, _) if x.1.is_none() => { Stmt::If(ref mut condition, ref mut x, _) if x.1.is_none() => {
optimize_expr(condition, state); optimize_expr(condition, state);
optimize_stmt(&mut x.0, state, true); optimize_stmt(&mut x.0, state, true);
} }
// if false { if_block } else { else_block } -> else_block // if false { if_block } else { else_block } -> else_block
Stmt::IfThenElse(Expr::False(_), x, _) if x.1.is_some() => { Stmt::If(Expr::False(_), x, _) if x.1.is_some() => {
*stmt = mem::take(x.1.as_mut().unwrap()); *stmt = mem::take(x.1.as_mut().unwrap());
optimize_stmt(stmt, state, true); optimize_stmt(stmt, state, true);
} }
// if true { if_block } else { else_block } -> if_block // if true { if_block } else { else_block } -> if_block
Stmt::IfThenElse(Expr::True(_), x, _) => { Stmt::If(Expr::True(_), x, _) => {
*stmt = mem::take(&mut x.0); *stmt = mem::take(&mut x.0);
optimize_stmt(stmt, state, true); optimize_stmt(stmt, state, true);
} }
// if expr { if_block } else { else_block } // if expr { if_block } else { else_block }
Stmt::IfThenElse(ref mut condition, ref mut x, _) => { Stmt::If(ref mut condition, ref mut x, _) => {
optimize_expr(condition, state); optimize_expr(condition, state);
optimize_stmt(&mut x.0, state, true); optimize_stmt(&mut x.0, state, true);
if let Some(else_block) = x.1.as_mut() { if let Some(else_block) = x.1.as_mut() {
@ -344,6 +340,42 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
} }
} }
// switch const { ... }
Stmt::Switch(expr, x, pos) if expr.is_constant() => {
let value = expr.get_constant_value().unwrap();
let hasher = &mut get_hasher();
value.hash(hasher);
let hash = hasher.finish();
state.set_dirty();
let table = &mut x.0;
if let Some(stmt) = table.get_mut(&hash) {
optimize_stmt(stmt, state, true);
*expr = Expr::Stmt(Box::new(vec![mem::take(stmt)].into()), *pos);
} else if let Some(def_stmt) = x.1.as_mut() {
optimize_stmt(def_stmt, state, true);
*expr = Expr::Stmt(Box::new(vec![mem::take(def_stmt)].into()), *pos);
} else {
*expr = Expr::Unit(*pos);
}
}
// switch
Stmt::Switch(expr, x, _) => {
optimize_expr(expr, state);
x.0.values_mut()
.for_each(|stmt| optimize_stmt(stmt, state, true));
if let Some(def_stmt) = x.1.as_mut() {
optimize_stmt(def_stmt, state, true);
match def_stmt {
Stmt::Noop(_) | Stmt::Expr(Expr::Unit(_)) => x.1 = None,
_ => (),
}
}
}
// while false { block } -> Noop // while false { block } -> Noop
Stmt::While(Expr::False(pos), _, _) => { Stmt::While(Expr::False(pos), _, _) => {
state.set_dirty(); state.set_dirty();
@ -435,7 +467,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
// expr; // expr;
Stmt::Expr(ref mut expr) => optimize_expr(expr, state), Stmt::Expr(ref mut expr) => optimize_expr(expr, state),
// return expr; // return expr;
Stmt::ReturnWithVal(_, Some(ref mut expr), _) => optimize_expr(expr, state), Stmt::Return(_, Some(ref mut expr), _) => optimize_expr(expr, state),
// All other statements - skip // All other statements - skip
_ => (), _ => (),
@ -523,12 +555,24 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
// lhs[rhs] // lhs[rhs]
(lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); } (lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); }
}, },
// [ constant .. ]
#[cfg(not(feature = "no_index"))]
Expr::Array(_, _) if expr.is_constant() => {
state.set_dirty();
*expr = Expr::DynamicConstant(Box::new(expr.get_constant_value().unwrap()), expr.position());
}
// [ items .. ] // [ items .. ]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Array(a, _) => a.iter_mut().for_each(|expr| optimize_expr(expr, state)), Expr::Array(x, _) => x.iter_mut().for_each(|expr| optimize_expr(expr, state)),
// #{ key:constant, .. }
#[cfg(not(feature = "no_object"))]
Expr::Map(_, _) if expr.is_constant()=> {
state.set_dirty();
*expr = Expr::DynamicConstant(Box::new(expr.get_constant_value().unwrap()), expr.position());
}
// #{ key:value, .. } // #{ key:value, .. }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Map(m, _) => m.iter_mut().for_each(|(_, expr)| optimize_expr(expr, state)), Expr::Map(x, _) => x.iter_mut().for_each(|(_, expr)| optimize_expr(expr, state)),
// lhs in rhs // lhs in rhs
Expr::In(x, _) => match (&mut x.lhs, &mut x.rhs) { Expr::In(x, _) => match (&mut x.lhs, &mut x.rhs) {
// "xxx" in "xxxxx" // "xxx" in "xxxxx"
@ -626,7 +670,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect(); let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect();
// Search for overloaded operators (can override built-in). // Search for overloaded operators (can override built-in).
if !state.engine.has_override_by_name_and_arguments(state.lib, x.name.as_ref(), arg_types.as_ref(), false) { if !state.engine.has_override_by_name_and_arguments(&state.engine.global_sub_modules, state.lib, x.name.as_ref(), arg_types.as_ref(), false) {
if let Some(result) = run_builtin_binary_op(x.name.as_ref(), &arg_values[0], &arg_values[1]) if let Some(result) = run_builtin_binary_op(x.name.as_ref(), &arg_values[0], &arg_values[1])
.ok().flatten() .ok().flatten()
.and_then(|result| map_dynamic_to_expr(result, *pos)) .and_then(|result| map_dynamic_to_expr(result, *pos))
@ -697,42 +741,6 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
*expr = result; *expr = result;
} }
// switch const { ... }
Expr::Switch(x, pos) if x.0.is_constant() => {
let value = x.0.get_constant_value().unwrap();
let hasher = &mut get_hasher();
value.hash(hasher);
let hash = hasher.finish();
state.set_dirty();
let table = &mut x.1;
if let Some(stmt) = table.get_mut(&hash) {
optimize_stmt(stmt, state, true);
*expr = Expr::Stmt(Box::new(vec![mem::take(stmt)].into()), *pos);
} else if let Some(def_stmt) = x.2.as_mut() {
optimize_stmt(def_stmt, state, true);
*expr = Expr::Stmt(Box::new(vec![mem::take(def_stmt)].into()), *pos);
} else {
*expr = Expr::Unit(*pos);
}
}
// switch
Expr::Switch(x, _) => {
optimize_expr(&mut x.0, state);
x.1.values_mut().for_each(|stmt| optimize_stmt(stmt, state, true));
if let Some(def_stmt) = x.2.as_mut() {
optimize_stmt(def_stmt, state, true);
match def_stmt {
Stmt::Noop(_) | Stmt::Expr(Expr::Unit(_)) => x.2 = None,
_ => ()
}
}
}
// Custom syntax // Custom syntax
Expr::Custom(x, _) => x.keywords.iter_mut().for_each(|expr| optimize_expr(expr, state)), Expr::Custom(x, _) => x.keywords.iter_mut().for_each(|expr| optimize_expr(expr, state)),
@ -742,7 +750,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
} }
fn optimize( fn optimize(
statements: Vec<Stmt>, mut statements: Vec<Stmt>,
engine: &Engine, engine: &Engine,
scope: &Scope, scope: &Scope,
lib: &[&Module], lib: &[&Module],
@ -750,6 +758,7 @@ fn optimize(
) -> Vec<Stmt> { ) -> Vec<Stmt> {
// If optimization level is None then skip optimizing // If optimization level is None then skip optimizing
if level == OptimizationLevel::None { if level == OptimizationLevel::None {
statements.shrink_to_fit();
return statements; return statements;
} }
@ -768,16 +777,14 @@ fn optimize(
let orig_constants_len = state.constants.len(); let orig_constants_len = state.constants.len();
let mut result = statements;
// Optimization loop // Optimization loop
loop { loop {
state.reset(); state.reset();
state.restore_constants(orig_constants_len); state.restore_constants(orig_constants_len);
let num_statements = result.len(); let num_statements = statements.len();
result.iter_mut().enumerate().for_each(|(i, stmt)| { statements.iter_mut().enumerate().for_each(|(i, stmt)| {
match stmt { match stmt {
Stmt::Const(var_def, expr, _, _) if expr.is_some() => { Stmt::Const(var_def, expr, _, _) if expr.is_some() => {
// Load constants // Load constants
@ -817,26 +824,27 @@ fn optimize(
} }
// Eliminate code that is pure but always keep the last statement // Eliminate code that is pure but always keep the last statement
let last_stmt = result.pop(); let last_stmt = statements.pop();
// Remove all pure statements at global level // Remove all pure statements at global level
result.retain(|stmt| !stmt.is_pure()); statements.retain(|stmt| !stmt.is_pure());
// Add back the last statement unless it is a lone No-op // Add back the last statement unless it is a lone No-op
if let Some(stmt) = last_stmt { if let Some(stmt) = last_stmt {
if !result.is_empty() || !stmt.is_noop() { if !statements.is_empty() || !stmt.is_noop() {
result.push(stmt); statements.push(stmt);
} }
} }
result statements.shrink_to_fit();
statements
} }
/// Optimize an AST. /// Optimize an AST.
pub fn optimize_into_ast( pub fn optimize_into_ast(
engine: &Engine, engine: &Engine,
scope: &Scope, scope: &Scope,
statements: Vec<Stmt>, mut statements: Vec<Stmt>,
_functions: Vec<ScriptFnDef>, _functions: Vec<ScriptFnDef>,
level: OptimizationLevel, level: OptimizationLevel,
) -> AST { ) -> AST {
@ -886,11 +894,11 @@ pub fn optimize_into_ast(
// {} -> Noop // {} -> Noop
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) { fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
// { return val; } -> val // { return val; } -> val
Stmt::ReturnWithVal((ReturnType::Return, _), Some(expr), _) => { Stmt::Return((crate::ast::ReturnType::Return, _), Some(expr), _) => {
Stmt::Expr(expr) Stmt::Expr(expr)
} }
// { return; } -> () // { return; } -> ()
Stmt::ReturnWithVal((ReturnType::Return, pos), None, _) => { Stmt::Return((crate::ast::ReturnType::Return, pos), None, _) => {
Stmt::Expr(Expr::Unit(pos)) Stmt::Expr(Expr::Unit(pos))
} }
// All others // All others
@ -913,6 +921,8 @@ pub fn optimize_into_ast(
#[cfg(feature = "no_function")] #[cfg(feature = "no_function")]
let lib = Default::default(); let lib = Default::default();
statements.shrink_to_fit();
AST::new( AST::new(
match level { match level {
OptimizationLevel::None => statements, OptimizationLevel::None => statements,

View File

@ -3,7 +3,7 @@
use crate::def_package; use crate::def_package;
use crate::dynamic::Dynamic; use crate::dynamic::Dynamic;
use crate::engine::{Array, OP_EQUALS}; use crate::engine::{Array, OP_EQUALS, TYPICAL_ARRAY_SIZE};
use crate::fn_native::{FnPtr, NativeCallContext}; use crate::fn_native::{FnPtr, NativeCallContext};
use crate::plugin::*; use crate::plugin::*;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
@ -12,9 +12,9 @@ use crate::utils::ImmutableString;
use crate::INT; use crate::INT;
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::engine::Map; use crate::Map;
use crate::stdlib::{any::TypeId, boxed::Box, cmp::Ordering, string::ToString}; use crate::stdlib::{any::TypeId, boxed::Box, cmp::max, cmp::Ordering, string::ToString};
pub type Unit = (); pub type Unit = ();
@ -54,7 +54,7 @@ macro_rules! gen_array_functions {
list.resize(len as usize, Dynamic::from(item)); list.resize(len as usize, Dynamic::from(item));
} }
Ok(().into()) Ok(Dynamic::UNIT)
} }
} }
})* } })* }
@ -201,7 +201,7 @@ mod array_functions {
list: &mut Array, list: &mut Array,
mapper: FnPtr, mapper: FnPtr,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
let mut array = Array::with_capacity(list.len()); let mut array = Array::with_capacity(max(TYPICAL_ARRAY_SIZE, list.len()));
for (i, item) in list.iter().enumerate() { for (i, item) in list.iter().enumerate() {
array.push( array.push(
@ -233,7 +233,7 @@ mod array_functions {
list: &mut Array, list: &mut Array,
filter: FnPtr, filter: FnPtr,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
let mut array = Array::with_capacity(list.len()); let mut array = Array::with_capacity(max(TYPICAL_ARRAY_SIZE, list.len()));
for (i, item) in list.iter().enumerate() { for (i, item) in list.iter().enumerate() {
if filter if filter
@ -367,7 +367,7 @@ mod array_functions {
list: &mut Array, list: &mut Array,
reducer: FnPtr, reducer: FnPtr,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
let mut result: Dynamic = ().into(); let mut result: Dynamic = Dynamic::UNIT;
for (i, item) in list.iter().enumerate() { for (i, item) in list.iter().enumerate() {
result = reducer result = reducer
@ -434,7 +434,7 @@ mod array_functions {
list: &mut Array, list: &mut Array,
reducer: FnPtr, reducer: FnPtr,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
let mut result: Dynamic = ().into(); let mut result: Dynamic = Dynamic::UNIT;
for (i, item) in list.iter().enumerate().rev() { for (i, item) in list.iter().enumerate().rev() {
result = reducer result = reducer
@ -529,7 +529,7 @@ mod array_functions {
}) })
}); });
Ok(().into()) Ok(Dynamic::UNIT)
} }
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn drain( pub fn drain(
@ -537,7 +537,7 @@ mod array_functions {
list: &mut Array, list: &mut Array,
filter: FnPtr, filter: FnPtr,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
let mut drained = Array::with_capacity(list.len()); let mut drained = Array::with_capacity(max(TYPICAL_ARRAY_SIZE, list.len()));
let mut i = list.len(); let mut i = list.len();
@ -596,7 +596,7 @@ mod array_functions {
list: &mut Array, list: &mut Array,
filter: FnPtr, filter: FnPtr,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
let mut drained = Array::with_capacity(list.len()); let mut drained = Array::with_capacity(max(TYPICAL_ARRAY_SIZE, list.len()));
let mut i = list.len(); let mut i = list.len();

View File

@ -8,7 +8,7 @@ use crate::utils::ImmutableString;
use crate::INT; use crate::INT;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::engine::Array; use crate::Array;
def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
combine_with_exported_module!(lib, "map", map_functions); combine_with_exported_module!(lib, "map", map_functions);

View File

@ -56,22 +56,20 @@ pub(crate) struct PackagesCollection(StaticVec<PackageLibrary>);
impl PackagesCollection { impl PackagesCollection {
/// Add a `PackageLibrary` into the `PackagesCollection`. /// Add a `PackageLibrary` into the `PackagesCollection`.
pub fn push(&mut self, package: PackageLibrary) { ///
/// Packages are searched in reverse order.
pub fn add(&mut self, package: PackageLibrary) {
// Later packages override previous ones. // Later packages override previous ones.
self.0.insert(0, package); self.0.insert(0, package);
} }
/// Does the specified function hash key exist in the `PackagesCollection`? /// Does the specified function hash key exist in the `PackagesCollection`?
#[allow(dead_code)] #[allow(dead_code)]
pub fn contains_fn(&self, hash: u64, public_only: bool) -> bool { pub fn contains_fn(&self, hash: u64) -> bool {
self.0.iter().any(|p| p.contains_fn(hash, public_only)) self.0.iter().any(|p| p.contains_fn(hash, false))
} }
/// Get specified function via its hash key. /// Get specified function via its hash key.
pub fn get_fn(&self, hash: u64, public_only: bool) -> Option<&CallableFunction> { pub fn get_fn(&self, hash: u64) -> Option<&CallableFunction> {
self.0 self.0.iter().find_map(|p| p.get_fn(hash, false))
.iter()
.map(|p| p.get_fn(hash, public_only))
.find(|f| f.is_some())
.flatten()
} }
/// Does the specified TypeId iterator exist in the `PackagesCollection`? /// Does the specified TypeId iterator exist in the `PackagesCollection`?
#[allow(dead_code)] #[allow(dead_code)]
@ -80,11 +78,7 @@ impl PackagesCollection {
} }
/// Get the specified TypeId iterator. /// Get the specified TypeId iterator.
pub fn get_iter(&self, id: TypeId) -> Option<IteratorFn> { pub fn get_iter(&self, id: TypeId) -> Option<IteratorFn> {
self.0 self.0.iter().find_map(|p| p.get_iter(id))
.iter()
.map(|p| p.get_iter(id))
.find(|f| f.is_some())
.flatten()
} }
} }
@ -128,7 +122,7 @@ macro_rules! def_package {
impl $package { impl $package {
pub fn new() -> Self { pub fn new() -> Self {
let mut module = $root::Module::new_with_capacity(512); let mut module = $root::Module::new_with_capacity(1024);
<Self as $root::packages::Package>::init(&mut module); <Self as $root::packages::Package>::init(&mut module);
Self(module.into()) Self(module.into())
} }

View File

@ -8,10 +8,10 @@ use crate::utils::ImmutableString;
use crate::INT; use crate::INT;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::engine::Array; use crate::Array;
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::engine::Map; use crate::Map;
use crate::stdlib::{ use crate::stdlib::{
fmt::{Debug, Display}, fmt::{Debug, Display},

View File

@ -8,9 +8,6 @@ use crate::utils::ImmutableString;
use crate::StaticVec; use crate::StaticVec;
use crate::INT; use crate::INT;
#[cfg(not(feature = "unchecked"))]
use crate::{result::EvalAltResult, token::NO_POS};
use crate::stdlib::{ use crate::stdlib::{
any::TypeId, boxed::Box, format, mem, string::String, string::ToString, vec::Vec, any::TypeId, boxed::Box, format, mem, string::String, string::ToString, vec::Vec,
}; };
@ -255,11 +252,15 @@ mod string_functions {
s: &mut ImmutableString, s: &mut ImmutableString,
len: INT, len: INT,
ch: char, ch: char,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<crate::EvalAltResult>> {
// Check if string will be over max size limit // Check if string will be over max size limit
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if _ctx.engine().max_string_size() > 0 && len as usize > _ctx.engine().max_string_size() { if _ctx.engine().max_string_size() > 0 && len as usize > _ctx.engine().max_string_size() {
return EvalAltResult::ErrorDataTooLarge("Length of string".to_string(), NO_POS).into(); return crate::EvalAltResult::ErrorDataTooLarge(
"Length of string".to_string(),
crate::NO_POS,
)
.into();
} }
if len > 0 { if len > 0 {
@ -275,16 +276,16 @@ mod string_functions {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if _ctx.engine().max_string_size() > 0 && s.len() > _ctx.engine().max_string_size() if _ctx.engine().max_string_size() > 0 && s.len() > _ctx.engine().max_string_size()
{ {
return EvalAltResult::ErrorDataTooLarge( return crate::EvalAltResult::ErrorDataTooLarge(
"Length of string".to_string(), "Length of string".to_string(),
NO_POS, crate::NO_POS,
) )
.into(); .into();
} }
} }
} }
Ok(().into()) Ok(Dynamic::UNIT)
} }
#[rhai_fn(name = "pad", return_raw)] #[rhai_fn(name = "pad", return_raw)]
pub fn pad_with_string( pub fn pad_with_string(
@ -292,11 +293,15 @@ mod string_functions {
s: &mut ImmutableString, s: &mut ImmutableString,
len: INT, len: INT,
padding: &str, padding: &str,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<crate::EvalAltResult>> {
// Check if string will be over max size limit // Check if string will be over max size limit
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if _ctx.engine().max_string_size() > 0 && len as usize > _ctx.engine().max_string_size() { if _ctx.engine().max_string_size() > 0 && len as usize > _ctx.engine().max_string_size() {
return EvalAltResult::ErrorDataTooLarge("Length of string".to_string(), NO_POS).into(); return crate::EvalAltResult::ErrorDataTooLarge(
"Length of string".to_string(),
crate::NO_POS,
)
.into();
} }
if len > 0 { if len > 0 {
@ -319,21 +324,21 @@ mod string_functions {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if _ctx.engine().max_string_size() > 0 && s.len() > _ctx.engine().max_string_size() if _ctx.engine().max_string_size() > 0 && s.len() > _ctx.engine().max_string_size()
{ {
return EvalAltResult::ErrorDataTooLarge( return crate::EvalAltResult::ErrorDataTooLarge(
"Length of string".to_string(), "Length of string".to_string(),
NO_POS, crate::NO_POS,
) )
.into(); .into();
} }
} }
} }
Ok(().into()) Ok(Dynamic::UNIT)
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub mod arrays { pub mod arrays {
use crate::engine::Array; use crate::Array;
#[rhai_fn(name = "+")] #[rhai_fn(name = "+")]
pub fn append(x: &str, y: Array) -> String { pub fn append(x: &str, y: Array) -> String {
@ -356,7 +361,7 @@ mod string_functions {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub mod maps { pub mod maps {
use crate::engine::Map; use crate::Map;
#[rhai_fn(name = "+")] #[rhai_fn(name = "+")]
pub fn append(x: &str, y: Map) -> String { pub fn append(x: &str, y: Map) -> String {

View File

@ -150,7 +150,7 @@ mod time_functions {
#[rhai_fn(return_raw, name = "+=")] #[rhai_fn(return_raw, name = "+=")]
pub fn add_assign(x: &mut Instant, seconds: FLOAT) -> Result<Dynamic, Box<EvalAltResult>> { pub fn add_assign(x: &mut Instant, seconds: FLOAT) -> Result<Dynamic, Box<EvalAltResult>> {
*x = add_impl(*x, seconds)?; *x = add_impl(*x, seconds)?;
Ok(().into()) Ok(Dynamic::UNIT)
} }
#[rhai_fn(return_raw, name = "-")] #[rhai_fn(return_raw, name = "-")]
pub fn subtract(x: Instant, seconds: FLOAT) -> Result<Dynamic, Box<EvalAltResult>> { pub fn subtract(x: Instant, seconds: FLOAT) -> Result<Dynamic, Box<EvalAltResult>> {
@ -162,7 +162,7 @@ mod time_functions {
seconds: FLOAT, seconds: FLOAT,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
*x = subtract_impl(*x, seconds)?; *x = subtract_impl(*x, seconds)?;
Ok(().into()) Ok(Dynamic::UNIT)
} }
} }
@ -204,7 +204,7 @@ mod time_functions {
#[rhai_fn(return_raw, name = "+=")] #[rhai_fn(return_raw, name = "+=")]
pub fn add_assign(x: &mut Instant, seconds: INT) -> Result<Dynamic, Box<EvalAltResult>> { pub fn add_assign(x: &mut Instant, seconds: INT) -> Result<Dynamic, Box<EvalAltResult>> {
*x = add_impl(*x, seconds)?; *x = add_impl(*x, seconds)?;
Ok(().into()) Ok(Dynamic::UNIT)
} }
#[rhai_fn(return_raw, name = "-")] #[rhai_fn(return_raw, name = "-")]
pub fn subtract(x: Instant, seconds: INT) -> Result<Dynamic, Box<EvalAltResult>> { pub fn subtract(x: Instant, seconds: INT) -> Result<Dynamic, Box<EvalAltResult>> {
@ -213,7 +213,7 @@ mod time_functions {
#[rhai_fn(return_raw, name = "-=")] #[rhai_fn(return_raw, name = "-=")]
pub fn subtract_assign(x: &mut Instant, seconds: INT) -> Result<Dynamic, Box<EvalAltResult>> { pub fn subtract_assign(x: &mut Instant, seconds: INT) -> Result<Dynamic, Box<EvalAltResult>> {
*x = subtract_impl(*x, seconds)?; *x = subtract_impl(*x, seconds)?;
Ok(().into()) Ok(Dynamic::UNIT)
} }
#[rhai_fn(name = "==")] #[rhai_fn(name = "==")]

View File

@ -19,15 +19,6 @@ use crate::{calc_script_fn_hash, StaticVec};
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::FLOAT; use crate::FLOAT;
#[cfg(not(feature = "no_object"))]
use crate::engine::{make_getter, make_setter, KEYWORD_EVAL, KEYWORD_FN_PTR};
#[cfg(not(feature = "no_function"))]
use crate::{
ast::FnAccess,
engine::{FN_ANONYMOUS, KEYWORD_FN_PTR_CURRY},
};
use crate::stdlib::{ use crate::stdlib::{
borrow::Cow, borrow::Cow,
boxed::Box, boxed::Box,
@ -41,9 +32,6 @@ use crate::stdlib::{
vec::Vec, vec::Vec,
}; };
#[cfg(not(feature = "no_closure"))]
use crate::stdlib::collections::HashSet;
type PERR = ParseErrorType; type PERR = ParseErrorType;
type FunctionsLib = HashMap<u64, ScriptFnDef, StraightHasherBuilder>; type FunctionsLib = HashMap<u64, ScriptFnDef, StraightHasherBuilder>;
@ -69,7 +57,7 @@ struct ParseState<'e> {
allow_capture: bool, allow_capture: bool,
/// Encapsulates a local stack with imported module names. /// Encapsulates a local stack with imported module names.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
modules: Vec<ImmutableString>, modules: StaticVec<ImmutableString>,
/// Maximum levels of expression nesting. /// Maximum levels of expression nesting.
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
max_expr_depth: usize, max_expr_depth: usize,
@ -102,8 +90,8 @@ impl<'e> ParseState<'e> {
externals: Default::default(), externals: Default::default(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
allow_capture: true, allow_capture: true,
strings: Default::default(), strings: HashMap::with_capacity(64),
stack: Default::default(), stack: Vec::with_capacity(16),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
modules: Default::default(), modules: Default::default(),
} }
@ -192,6 +180,8 @@ struct ParseSettings {
allow_anonymous_fn: bool, allow_anonymous_fn: bool,
/// Is if-expression allowed? /// Is if-expression allowed?
allow_if_expr: bool, allow_if_expr: bool,
/// Is switch expression allowed?
allow_switch_expr: bool,
/// Is statement-expression allowed? /// Is statement-expression allowed?
allow_stmt_expr: bool, allow_stmt_expr: bool,
/// Current expression nesting level. /// Current expression nesting level.
@ -229,8 +219,8 @@ impl Expr {
match self { match self {
Self::Variable(x) if x.1.is_none() => { Self::Variable(x) if x.1.is_none() => {
let ident = x.3; let ident = x.3;
let getter = state.get_interned_string(make_getter(&ident.name)); let getter = state.get_interned_string(crate::engine::make_getter(&ident.name));
let setter = state.get_interned_string(make_setter(&ident.name)); let setter = state.get_interned_string(crate::engine::make_setter(&ident.name));
Self::Property(Box::new(((getter, setter), ident.into()))) Self::Property(Box::new(((getter, setter), ident.into())))
} }
_ => self, _ => self,
@ -793,8 +783,12 @@ fn parse_switch(
input: &mut TokenStream, input: &mut TokenStream,
state: &mut ParseState, state: &mut ParseState,
lib: &mut FunctionsLib, lib: &mut FunctionsLib,
settings: ParseSettings, mut settings: ParseSettings,
) -> Result<Expr, ParseError> { ) -> Result<Stmt, ParseError> {
// switch ...
let token_pos = eat_token(input, Token::Switch);
settings.pos = token_pos;
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
@ -812,7 +806,7 @@ fn parse_switch(
} }
} }
let mut table: HashMap<u64, Stmt, StraightHasherBuilder> = Default::default(); let mut table = HashMap::with_capacity_and_hasher(16, StraightHasherBuilder);
let mut def_stmt = None; let mut def_stmt = None;
loop { loop {
@ -901,8 +895,9 @@ fn parse_switch(
} }
} }
Ok(Expr::Switch( Ok(Stmt::Switch(
Box::new((item, table, def_stmt)), item,
Box::new((table, def_stmt)),
settings.pos, settings.pos,
)) ))
} }
@ -953,7 +948,7 @@ fn parse_primary(
let var_name_def = IdentX::new(state.get_interned_string(s), settings.pos); let var_name_def = IdentX::new(state.get_interned_string(s), settings.pos);
Expr::Variable(Box::new((None, None, 0, var_name_def))) Expr::Variable(Box::new((None, None, 0, var_name_def)))
} }
// Module qualification // Namespace qualification
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Token::Identifier(s) if *next_token == Token::DoubleColon => { Token::Identifier(s) if *next_token == Token::DoubleColon => {
// Once the identifier consumed we must enable next variables capturing // Once the identifier consumed we must enable next variables capturing
@ -1004,7 +999,6 @@ fn parse_primary(
Token::LeftBracket => parse_array_literal(input, state, lib, settings.level_up())?, Token::LeftBracket => parse_array_literal(input, state, lib, settings.level_up())?,
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Token::MapStart => parse_map_literal(input, state, lib, settings.level_up())?, Token::MapStart => parse_map_literal(input, state, lib, settings.level_up())?,
Token::Switch => parse_switch(input, state, lib, settings.level_up())?,
Token::True => Expr::True(settings.pos), Token::True => Expr::True(settings.pos),
Token::False => Expr::False(settings.pos), Token::False => Expr::False(settings.pos),
Token::LexError(err) => return Err(err.into_err(settings.pos)), Token::LexError(err) => return Err(err.into_err(settings.pos)),
@ -1131,11 +1125,15 @@ fn parse_unary(
match token { match token {
// If statement is allowed to act as expressions // If statement is allowed to act as expressions
Token::If if settings.allow_if_expr => { Token::If if settings.allow_if_expr => Ok(Expr::Stmt(
let mut block: StaticVec<_> = Default::default(); Box::new(vec![parse_if(input, state, lib, settings.level_up())?].into()),
block.push(parse_if(input, state, lib, settings.level_up())?); settings.pos,
Ok(Expr::Stmt(Box::new(block), settings.pos)) )),
} // Switch statement is allowed to act as expressions
Token::Switch if settings.allow_switch_expr => Ok(Expr::Stmt(
Box::new(vec![parse_switch(input, state, lib, settings.level_up())?].into()),
settings.pos,
)),
// -expr // -expr
Token::UnaryMinus => { Token::UnaryMinus => {
let pos = eat_token(input, Token::UnaryMinus); let pos = eat_token(input, Token::UnaryMinus);
@ -1219,6 +1217,7 @@ fn parse_unary(
let settings = ParseSettings { let settings = ParseSettings {
allow_if_expr: true, allow_if_expr: true,
allow_switch_expr: true,
allow_stmt_expr: true, allow_stmt_expr: true,
allow_anonymous_fn: true, allow_anonymous_fn: true,
is_global: false, is_global: false,
@ -1381,8 +1380,8 @@ fn make_dot_expr(
// lhs.id // lhs.id
(lhs, Expr::Variable(x)) if x.1.is_none() => { (lhs, Expr::Variable(x)) if x.1.is_none() => {
let ident = x.3; let ident = x.3;
let getter = state.get_interned_string(make_getter(&ident.name)); let getter = state.get_interned_string(crate::engine::make_getter(&ident.name));
let setter = state.get_interned_string(make_setter(&ident.name)); let setter = state.get_interned_string(crate::engine::make_setter(&ident.name));
let rhs = Expr::Property(Box::new(((getter, setter), ident))); let rhs = Expr::Property(Box::new(((getter, setter), ident)));
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos) Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)
@ -1419,7 +1418,7 @@ fn make_dot_expr(
} }
// lhs.Fn() or lhs.eval() // lhs.Fn() or lhs.eval()
(_, Expr::FnCall(x, pos)) (_, Expr::FnCall(x, pos))
if x.args.len() == 0 && [KEYWORD_FN_PTR, KEYWORD_EVAL].contains(&x.name.as_ref()) => if x.args.len() == 0 && [crate::engine::KEYWORD_FN_PTR, crate::engine::KEYWORD_EVAL].contains(&x.name.as_ref()) =>
{ {
return Err(PERR::BadInput(LexError::ImproperSymbol(format!( return Err(PERR::BadInput(LexError::ImproperSymbol(format!(
"'{}' should not be called in method style. Try {}(...);", "'{}' should not be called in method style. Try {}(...);",
@ -1970,11 +1969,7 @@ fn parse_if(
None None
}; };
Ok(Stmt::IfThenElse( Ok(Stmt::If(guard, Box::new((if_body, else_body)), token_pos))
guard,
Box::new((if_body, else_body)),
token_pos,
))
} }
/// Parse a while loop. /// Parse a while loop.
@ -2204,7 +2199,7 @@ fn parse_export(
_ => (), _ => (),
} }
let mut exports = Vec::new(); let mut exports = Vec::with_capacity(4);
loop { loop {
let (id, id_pos) = match input.next().unwrap() { let (id, id_pos) = match input.next().unwrap() {
@ -2272,7 +2267,7 @@ fn parse_block(
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
let mut statements = Vec::new(); let mut statements = Vec::with_capacity(8);
let prev_stack_len = state.stack.len(); let prev_stack_len = state.stack.len();
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -2374,9 +2369,9 @@ fn parse_stmt(
Token::Fn | Token::Private => { Token::Fn | Token::Private => {
let access = if matches!(token, Token::Private) { let access = if matches!(token, Token::Private) {
eat_token(input, Token::Private); eat_token(input, Token::Private);
FnAccess::Private crate::FnAccess::Private
} else { } else {
FnAccess::Public crate::FnAccess::Public
}; };
match input.next().unwrap() { match input.next().unwrap() {
@ -2392,6 +2387,7 @@ fn parse_stmt(
let settings = ParseSettings { let settings = ParseSettings {
allow_if_expr: true, allow_if_expr: true,
allow_switch_expr: true,
allow_stmt_expr: true, allow_stmt_expr: true,
allow_anonymous_fn: true, allow_anonymous_fn: true,
is_global: false, is_global: false,
@ -2451,13 +2447,9 @@ fn parse_stmt(
match input.peek().unwrap() { match input.peek().unwrap() {
// `return`/`throw` at <EOF> // `return`/`throw` at <EOF>
(Token::EOF, pos) => Ok(Some(Stmt::ReturnWithVal( (Token::EOF, pos) => Ok(Some(Stmt::Return((return_type, token_pos), None, *pos))),
(return_type, token_pos),
None,
*pos,
))),
// `return;` or `throw;` // `return;` or `throw;`
(Token::SemiColon, _) => Ok(Some(Stmt::ReturnWithVal( (Token::SemiColon, _) => Ok(Some(Stmt::Return(
(return_type, token_pos), (return_type, token_pos),
None, None,
settings.pos, settings.pos,
@ -2466,7 +2458,7 @@ fn parse_stmt(
(_, _) => { (_, _) => {
let expr = parse_expr(input, state, lib, settings.level_up())?; let expr = parse_expr(input, state, lib, settings.level_up())?;
let pos = expr.position(); let pos = expr.position();
Ok(Some(Stmt::ReturnWithVal( Ok(Some(Stmt::Return(
(return_type, token_pos), (return_type, token_pos),
Some(expr), Some(expr),
pos, pos,
@ -2560,7 +2552,7 @@ fn parse_fn(
input: &mut TokenStream, input: &mut TokenStream,
state: &mut ParseState, state: &mut ParseState,
lib: &mut FunctionsLib, lib: &mut FunctionsLib,
access: FnAccess, access: crate::FnAccess,
mut settings: ParseSettings, mut settings: ParseSettings,
) -> Result<ScriptFnDef, ParseError> { ) -> Result<ScriptFnDef, ParseError> {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
@ -2580,7 +2572,7 @@ fn parse_fn(
(_, pos) => return Err(PERR::FnMissingParams(name).into_err(*pos)), (_, pos) => return Err(PERR::FnMissingParams(name).into_err(*pos)),
}; };
let mut params = Vec::new(); let mut params: StaticVec<_> = Default::default();
if !match_token(input, Token::RightParen).0 { if !match_token(input, Token::RightParen).0 {
let sep_err = format!("to separate the parameters of function '{}'", name); let sep_err = format!("to separate the parameters of function '{}'", name);
@ -2590,7 +2582,7 @@ fn parse_fn(
(Token::RightParen, _) => break, (Token::RightParen, _) => break,
(Token::Identifier(s), pos) => { (Token::Identifier(s), pos) => {
if params.iter().any(|(p, _)| p == &s) { if params.iter().any(|(p, _)| p == &s) {
return Err(PERR::FnDuplicatedParam(name.to_string(), s).into_err(pos)); return Err(PERR::FnDuplicatedParam(name, s).into_err(pos));
} }
let s = state.get_interned_string(s); let s = state.get_interned_string(s);
state.stack.push((s.clone(), ScopeEntryType::Normal)); state.stack.push((s.clone(), ScopeEntryType::Normal));
@ -2629,7 +2621,7 @@ fn parse_fn(
let params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect(); let params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect();
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
let externals: HashSet<_> = state let externals = state
.externals .externals
.iter() .iter()
.map(|(name, _)| name) .map(|(name, _)| name)
@ -2672,11 +2664,11 @@ fn make_curry_from_externals(fn_expr: Expr, externals: StaticVec<IdentX>, pos: P
args.push(Expr::Variable(Box::new((None, None, 0, x.clone().into())))); args.push(Expr::Variable(Box::new((None, None, 0, x.clone().into()))));
}); });
let hash = calc_script_fn_hash(empty(), KEYWORD_FN_PTR_CURRY, num_externals + 1); let hash = calc_script_fn_hash(empty(), crate::engine::KEYWORD_FN_PTR_CURRY, num_externals + 1);
let expr = Expr::FnCall( let expr = Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
name: KEYWORD_FN_PTR_CURRY.into(), name: crate::engine::KEYWORD_FN_PTR_CURRY.into(),
hash, hash,
args, args,
..Default::default() ..Default::default()
@ -2712,7 +2704,7 @@ fn parse_anon_fn(
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
let mut params = Vec::new(); let mut params: StaticVec<_> = Default::default();
if input.next().unwrap().0 != Token::Or { if input.next().unwrap().0 != Token::Or {
if !match_token(input, Token::Pipe).0 { if !match_token(input, Token::Pipe).0 {
@ -2788,12 +2780,12 @@ fn parse_anon_fn(
settings.pos.hash(hasher); settings.pos.hash(hasher);
let hash = hasher.finish(); let hash = hasher.finish();
let fn_name: ImmutableString = format!("{}{:016x}", FN_ANONYMOUS, hash).into(); let fn_name: ImmutableString = format!("{}{:016x}", crate::engine::FN_ANONYMOUS, hash).into();
// Define the function // Define the function
let script = ScriptFnDef { let script = ScriptFnDef {
name: fn_name.clone(), name: fn_name.clone(),
access: FnAccess::Public, access: crate::FnAccess::Public,
params, params,
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
externals: Default::default(), externals: Default::default(),
@ -2835,6 +2827,7 @@ impl Engine {
let settings = ParseSettings { let settings = ParseSettings {
allow_if_expr: false, allow_if_expr: false,
allow_switch_expr: false,
allow_stmt_expr: false, allow_stmt_expr: false,
allow_anonymous_fn: false, allow_anonymous_fn: false,
is_global: true, is_global: true,
@ -2872,8 +2865,8 @@ impl Engine {
script_hash: u64, script_hash: u64,
input: &mut TokenStream, input: &mut TokenStream,
) -> Result<(Vec<Stmt>, Vec<ScriptFnDef>), ParseError> { ) -> Result<(Vec<Stmt>, Vec<ScriptFnDef>), ParseError> {
let mut statements: Vec<Stmt> = Default::default(); let mut statements = Vec::with_capacity(16);
let mut functions = Default::default(); let mut functions = HashMap::with_capacity_and_hasher(16, StraightHasherBuilder);
let mut state = ParseState::new( let mut state = ParseState::new(
self, self,
script_hash, script_hash,
@ -2887,6 +2880,7 @@ impl Engine {
while !input.peek().unwrap().0.is_eof() { while !input.peek().unwrap().0.is_eof() {
let settings = ParseSettings { let settings = ParseSettings {
allow_if_expr: true, allow_if_expr: true,
allow_switch_expr: true,
allow_stmt_expr: true, allow_stmt_expr: true,
allow_anonymous_fn: true, allow_anonymous_fn: true,
is_global: true, is_global: true,

View File

@ -6,9 +6,6 @@ use crate::token::{Position, NO_POS};
use crate::utils::ImmutableString; use crate::utils::ImmutableString;
use crate::INT; use crate::INT;
#[cfg(not(feature = "no_function"))]
use crate::engine::is_anonymous_fn;
use crate::stdlib::{ use crate::stdlib::{
boxed::Box, boxed::Box,
error::Error, error::Error,
@ -159,7 +156,7 @@ impl fmt::Display for EvalAltResult {
Self::ErrorParsing(p, _) => write!(f, "Syntax error: {}", p)?, Self::ErrorParsing(p, _) => write!(f, "Syntax error: {}", p)?,
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Self::ErrorInFunctionCall(s, err, _) if is_anonymous_fn(s) => { Self::ErrorInFunctionCall(s, err, _) if crate::engine::is_anonymous_fn(s) => {
write!(f, "Error in call to closure: {}", err)? write!(f, "Error in call to closure: {}", err)?
} }
Self::ErrorInFunctionCall(s, err, _) => { Self::ErrorInFunctionCall(s, err, _) => {
@ -303,7 +300,6 @@ impl EvalAltResult {
Self::LoopBreak(_, _) | Self::Return(_, _) => unreachable!(), Self::LoopBreak(_, _) | Self::Return(_, _) => unreachable!(),
} }
} }
/// Is this error a system exception? /// Is this error a system exception?
pub fn is_system_exception(&self) -> bool { pub fn is_system_exception(&self) -> bool {
match self { match self {
@ -322,7 +318,6 @@ impl EvalAltResult {
_ => false, _ => false,
} }
} }
/// Get the `Position` of this error. /// Get the `Position` of this error.
pub fn position(&self) -> Position { pub fn position(&self) -> Position {
match self { match self {
@ -356,7 +351,6 @@ impl EvalAltResult {
| Self::Return(_, pos) => *pos, | Self::Return(_, pos) => *pos,
} }
} }
/// Override the `Position` of this error. /// Override the `Position` of this error.
pub fn set_position(&mut self, new_position: Position) { pub fn set_position(&mut self, new_position: Position) {
match self { match self {
@ -390,7 +384,6 @@ impl EvalAltResult {
| Self::Return(_, pos) => *pos = new_position, | Self::Return(_, pos) => *pos = new_position,
} }
} }
/// Consume the current `EvalAltResult` and return a new one with the specified `Position` /// Consume the current `EvalAltResult` and return a new one with the specified `Position`
/// if the current position is `Position::None`. /// if the current position is `Position::None`.
#[inline(always)] #[inline(always)]

View File

@ -65,7 +65,7 @@ impl EntryType {
// //
// Since `Dynamic` is reasonably small, packing it tightly improves cache locality when variables are accessed. // Since `Dynamic` is reasonably small, packing it tightly improves cache locality when variables are accessed.
// The variable type is packed separately into another array because it is even smaller. // The variable type is packed separately into another array because it is even smaller.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone)]
pub struct Scope<'a> { pub struct Scope<'a> {
/// Current value of the entry. /// Current value of the entry.
values: Vec<Dynamic>, values: Vec<Dynamic>,
@ -75,6 +75,16 @@ pub struct Scope<'a> {
names: Vec<(Cow<'a, str>, Box<StaticVec<String>>)>, names: Vec<(Cow<'a, str>, Box<StaticVec<String>>)>,
} }
impl Default for Scope<'_> {
fn default() -> Self {
Self {
values: Vec::with_capacity(16),
types: Vec::with_capacity(16),
names: Vec::with_capacity(16),
}
}
}
impl<'a> Scope<'a> { impl<'a> Scope<'a> {
/// Create a new Scope. /// Create a new Scope.
/// ///
@ -417,6 +427,7 @@ impl<'a> Scope<'a> {
} }
/// Get an iterator to entries in the Scope. /// Get an iterator to entries in the Scope.
#[inline(always)] #[inline(always)]
#[allow(dead_code)]
pub(crate) fn into_iter( pub(crate) fn into_iter(
self, self,
) -> impl Iterator<Item = (Cow<'a, str>, EntryType, Dynamic, Vec<String>)> { ) -> impl Iterator<Item = (Cow<'a, str>, EntryType, Dynamic, Vec<String>)> {

View File

@ -13,13 +13,10 @@ use serde::de::{
use serde::Deserialize; use serde::Deserialize;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::engine::Array; use crate::Array;
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::engine::Map; use crate::Map;
#[cfg(not(feature = "no_object"))]
use serde::de::{EnumAccess, VariantAccess};
use crate::stdlib::{any::type_name, boxed::Box, fmt, string::ToString}; use crate::stdlib::{any::type_name, boxed::Box, fmt, string::ToString};
@ -542,7 +539,7 @@ struct EnumDeserializer<'t, 'de: 't> {
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
impl<'t, 'de> EnumAccess<'de> for EnumDeserializer<'t, 'de> { impl<'t, 'de> serde::de::EnumAccess<'de> for EnumDeserializer<'t, 'de> {
type Error = Box<EvalAltResult>; type Error = Box<EvalAltResult>;
type Variant = Self; type Variant = Self;
@ -556,7 +553,7 @@ impl<'t, 'de> EnumAccess<'de> for EnumDeserializer<'t, 'de> {
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
impl<'t, 'de> VariantAccess<'de> for EnumDeserializer<'t, 'de> { impl<'t, 'de> serde::de::VariantAccess<'de> for EnumDeserializer<'t, 'de> {
type Error = Box<EvalAltResult>; type Error = Box<EvalAltResult>;
fn unit_variant(mut self) -> Result<(), Self::Error> { fn unit_variant(mut self) -> Result<(), Self::Error> {

View File

@ -5,9 +5,10 @@ use crate::result::EvalAltResult;
use crate::token::NO_POS; use crate::token::NO_POS;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::engine::Array; use crate::Array;
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::engine::Map; use crate::Map;
use serde::ser::{ use serde::ser::{
Error, SerializeMap, SerializeSeq, SerializeStruct, SerializeTuple, SerializeTupleStruct, Error, SerializeMap, SerializeSeq, SerializeStruct, SerializeTuple, SerializeTupleStruct,
@ -15,17 +16,8 @@ use serde::ser::{
}; };
use serde::Serialize; use serde::Serialize;
#[cfg(not(any(feature = "no_object", feature = "no_index")))]
use serde::ser::SerializeTupleVariant;
#[cfg(not(feature = "no_object"))]
use serde::ser::SerializeStructVariant;
use crate::stdlib::{boxed::Box, fmt, string::ToString}; use crate::stdlib::{boxed::Box, fmt, string::ToString};
#[cfg(not(feature = "no_object"))]
use crate::stdlib::mem;
/// Serializer for `Dynamic` which is kept as a reference. /// Serializer for `Dynamic` which is kept as a reference.
pub struct DynamicSerializer { pub struct DynamicSerializer {
/// Buffer to hold a temporary key. /// Buffer to hold a temporary key.
@ -247,7 +239,7 @@ impl Serializer for &mut DynamicSerializer {
} }
fn serialize_none(self) -> Result<Self::Ok, Box<EvalAltResult>> { fn serialize_none(self) -> Result<Self::Ok, Box<EvalAltResult>> {
Ok(().into()) Ok(Dynamic::UNIT)
} }
fn serialize_some<T: ?Sized + Serialize>( fn serialize_some<T: ?Sized + Serialize>(
@ -258,7 +250,7 @@ impl Serializer for &mut DynamicSerializer {
} }
fn serialize_unit(self) -> Result<Self::Ok, Box<EvalAltResult>> { fn serialize_unit(self) -> Result<Self::Ok, Box<EvalAltResult>> {
Ok(().into()) Ok(Dynamic::UNIT)
} }
fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok, Box<EvalAltResult>> { fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok, Box<EvalAltResult>> {
@ -477,7 +469,7 @@ impl SerializeMap for DynamicSerializer {
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
{ {
let key = mem::take(&mut self._key) let key = crate::stdlib::mem::take(&mut self._key)
.take_immutable_string() .take_immutable_string()
.map_err(|typ| { .map_err(|typ| {
EvalAltResult::ErrorMismatchOutputType("string".into(), typ.into(), NO_POS) EvalAltResult::ErrorMismatchOutputType("string".into(), typ.into(), NO_POS)
@ -554,7 +546,7 @@ pub struct TupleVariantSerializer {
} }
#[cfg(not(any(feature = "no_object", feature = "no_index")))] #[cfg(not(any(feature = "no_object", feature = "no_index")))]
impl SerializeTupleVariant for TupleVariantSerializer { impl serde::ser::SerializeTupleVariant for TupleVariantSerializer {
type Ok = Dynamic; type Ok = Dynamic;
type Error = Box<EvalAltResult>; type Error = Box<EvalAltResult>;
@ -579,7 +571,7 @@ pub struct StructVariantSerializer {
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
impl SerializeStructVariant for StructVariantSerializer { impl serde::ser::SerializeStructVariant for StructVariantSerializer {
type Ok = Dynamic; type Ok = Dynamic;
type Error = Box<EvalAltResult>; type Error = Box<EvalAltResult>;

View File

@ -193,7 +193,6 @@ impl Engine {
Ok(self) Ok(self)
} }
/// Register a custom syntax with the `Engine`. /// Register a custom syntax with the `Engine`.
/// ///
/// ## WARNING - Low Level API /// ## WARNING - Low Level API

View File

@ -5,9 +5,6 @@ use crate::engine::{
KEYWORD_IS_DEF_FN, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF, KEYWORD_IS_DEF_FN, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
}; };
#[cfg(not(feature = "no_closure"))]
use crate::engine::KEYWORD_IS_SHARED;
use crate::parse_error::LexError; use crate::parse_error::LexError;
use crate::StaticVec; use crate::StaticVec;
use crate::INT; use crate::INT;
@ -545,7 +542,7 @@ impl Token {
| KEYWORD_IS_DEF_FN | KEYWORD_THIS => Reserved(syntax.into()), | KEYWORD_IS_DEF_FN | KEYWORD_THIS => Reserved(syntax.into()),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
KEYWORD_IS_SHARED => Reserved(syntax.into()), crate::engine::KEYWORD_IS_SHARED => Reserved(syntax.into()),
_ => return None, _ => return None,
}) })
@ -1515,7 +1512,7 @@ fn get_identifier(
pub fn is_keyword_function(name: &str) -> bool { pub fn is_keyword_function(name: &str) -> bool {
match name { match name {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
KEYWORD_IS_SHARED => true, crate::engine::KEYWORD_IS_SHARED => true,
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_DEF_VAR | KEYWORD_IS_DEF_FN => { | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_DEF_VAR | KEYWORD_IS_DEF_FN => {
true true

View File

@ -1,14 +1,25 @@
use rhai::{Engine, INT}; use rhai::{Engine, EvalAltResult, INT};
#[test] #[test]
fn test_comments() { fn test_comments() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();
assert!(engine assert_eq!(
.eval::<INT>("let x = 5; x // I am a single line comment, yay!") engine.eval::<INT>("let x = 42; x // I am a single line comment, yay!")?,
.is_ok()); 42
);
assert!(engine assert_eq!(
.eval::<INT>("let /* I am a multi-line comment, yay! */ x = 5; x") engine.eval::<INT>(
.is_ok()); r#"
let /* I am a
multi-line
comment, yay!
*/ x = 42; x
"#
)?,
42
);
Ok(())
} }

View File

@ -1,4 +1,4 @@
use rhai::{Engine, EvalAltResult, INT}; use rhai::{Engine, EvalAltResult, Module, INT};
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
#[test] #[test]
@ -75,3 +75,45 @@ fn test_for_object() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[derive(Debug, Clone)]
struct MyIterableType(String);
impl IntoIterator for MyIterableType {
type Item = char;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.0.chars().collect::<Vec<_>>().into_iter()
}
}
#[cfg(not(feature = "no_module"))]
#[test]
fn test_for_module_iterator() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
// Set a type iterator deep inside a nested module chain
let mut sub_module = Module::new();
sub_module.set_iterable::<MyIterableType>();
sub_module.set_fn_0("new_ts", || Ok(MyIterableType("hello".to_string())));
let mut module = Module::new();
module.set_sub_module("inner", sub_module);
engine.register_module("testing", module);
let script = r#"
let item = testing::inner::new_ts();
let result = "";
for x in item {
result += x;
}
result
"#;
assert_eq!(engine.eval::<String>(script)?, "hello");
Ok(())
}

View File

@ -23,6 +23,7 @@ fn test_module_sub_module() -> Result<(), Box<EvalAltResult>> {
sub_module2.set_var("answer", 41 as INT); sub_module2.set_var("answer", 41 as INT);
let hash_inc = sub_module2.set_fn_1("inc", |x: INT| Ok(x + 1)); let hash_inc = sub_module2.set_fn_1("inc", |x: INT| Ok(x + 1));
sub_module2.set_fn_1_mut("super_inc", |x: &mut INT| Ok(*x + 1));
sub_module.set_sub_module("universe", sub_module2); sub_module.set_sub_module("universe", sub_module2);
module.set_sub_module("life", sub_module); module.set_sub_module("life", sub_module);
@ -39,26 +40,32 @@ fn test_module_sub_module() -> Result<(), Box<EvalAltResult>> {
assert_eq!(m2.get_var_value::<INT>("answer").unwrap(), 41); assert_eq!(m2.get_var_value::<INT>("answer").unwrap(), 41);
let mut resolver = StaticModuleResolver::new();
resolver.insert("question", module);
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.set_module_resolver(Some(resolver)); engine.register_module("question", module);
assert_eq!(engine.eval::<INT>("question::MYSTIC_NUMBER")?, 42);
assert!(engine.eval::<INT>("MYSTIC_NUMBER").is_err());
assert_eq!( assert_eq!(
engine.eval::<INT>(r#"import "question" as q; q::MYSTIC_NUMBER"#)?, engine.eval::<INT>("question::life::universe::answer + 1")?,
42 42
); );
assert_eq!( assert_eq!(
engine.eval::<INT>(r#"import "question" as q; q::life::universe::answer + 1"#)?, engine.eval::<INT>("question::life::universe::inc(question::life::universe::answer)")?,
42 42
); );
assert!(engine
.eval::<INT>("inc(question::life::universe::answer)")
.is_err());
#[cfg(not(feature = "no_object"))]
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>("super_inc(question::life::universe::answer)")?,
r#"import "question" as q; q::life::universe::inc(q::life::universe::answer)"#
)?,
42 42
); );
#[cfg(feature = "no_object")]
assert!(engine
.eval::<INT>("super_inc(question::life::universe::answer)")
.is_err());
Ok(()) Ok(())
} }

View File

@ -62,3 +62,59 @@ fn test_switch() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))]
mod test_switch_enum {
use super::*;
use rhai::Array;
#[derive(Debug, Clone)]
enum MyEnum {
Foo,
Bar(INT),
Baz(String, bool),
}
impl MyEnum {
fn get_enum_data(&mut self) -> Array {
match self {
Self::Foo => vec!["Foo".into()] as Array,
Self::Bar(num) => vec!["Bar".into(), (*num).into()] as Array,
Self::Baz(name, option) => {
vec!["Baz".into(), name.clone().into(), (*option).into()] as Array
}
}
}
}
#[test]
fn test_switch_enum() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
engine
.register_type_with_name::<MyEnum>("MyEnum")
.register_get("get_data", MyEnum::get_enum_data);
let mut scope = Scope::new();
scope.push("x", MyEnum::Baz("hello".to_string(), true));
assert_eq!(
engine.eval_with_scope::<INT>(
&mut scope,
r#"
switch x.get_data {
["Foo"] => 1,
["Bar", 42] => 2,
["Bar", 123] => 3,
["Baz", "hello", false] => 4,
["Baz", "hello", true] => 5,
_ => 9
}
"#
)?,
5
);
Ok(())
}
}

View File

@ -1,4 +1,4 @@
use rhai::{Engine, EvalAltResult, LexError, ParseError, ParseErrorType, INT, NO_POS}; use rhai::{Dynamic, Engine, EvalAltResult, LexError, ParseError, ParseErrorType, INT, NO_POS};
#[test] #[test]
fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> { fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
@ -48,7 +48,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
} }
} }
Ok(().into()) Ok(Dynamic::UNIT)
}, },
)?; )?;
@ -65,7 +65,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
// The first symbol must be an identifier // The first symbol must be an identifier
assert_eq!( assert_eq!(
*engine *engine
.register_custom_syntax(&["!"], 0, |_, _| Ok(().into())) .register_custom_syntax(&["!"], 0, |_, _| Ok(Dynamic::UNIT))
.expect_err("should error") .expect_err("should error")
.0, .0,
ParseErrorType::BadInput(LexError::ImproperSymbol( ParseErrorType::BadInput(LexError::ImproperSymbol(