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.
* `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

View File

@ -156,3 +156,61 @@ fn bench_eval_loop_strings_no_build(bench: &mut Bencher) {
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 mod_name = mod_all.ident.clone();
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 {
// Generate new module items.

View File

@ -117,13 +117,14 @@ The Rhai Scripting Language
6. [Subtle Semantic Changes](engine/optimize/semantics.md)
8. [Usage Patterns](patterns/index.md)
1. [Object-Oriented Programming (OOP)](patterns/oop.md)
2. [Loadable Configuration](patterns/config.md)
3. [Control Layer](patterns/control.md)
4. [Singleton Command](patterns/singleton.md)
5. [Multi-Layer Functions](patterns/multi-layer.md)
6. [One Engine Instance Per Call](patterns/parallel.md)
7. [Scriptable Event Handler with State](patterns/events.md)
8. [Dynamic Constants Provider](patterns/dynamic-const.md)
2. [Working With Rust Enums](patterns/enums.md)
3. [Loadable Configuration](patterns/config.md)
4. [Control Layer](patterns/control.md)
5. [Singleton Command](patterns/singleton.md)
6. [Multi-Layer Functions](patterns/multi-layer.md)
7. [One Engine Instance Per Call](patterns/parallel.md)
8. [Scriptable Event Handler with State](patterns/events.md)
9. [Dynamic Constants Provider](patterns/dynamic-const.md)
9. [Advanced Topics](advanced.md)
1. [Capture Scope for Function Call](language/fn-capture.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 |
| Logic and bit masks | <code>\|\|</code>, <code>\|</code>, `^` | 30 |
| Logic and bit masks | `&`, `&&` | 60 |
| Logic and bit masks | `&&`, `&` | 60 |
| Comparisons | `==`, `!=` | 90 |
| | `in` | 110 |
| 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;

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.
### Look-up Table vs `x == y`
A `switch` expression matches through _hashing_ via a look-up table.
Therefore, matching is very fast. Walking down an `if`-`else` chain
will be _much_ slower.
Therefore, matching is very fast. Walking down an `if`-`else if` chain
is _much_ slower.
On the other hand, operators can be [overloaded][operator overloading] in Rhai,
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;
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]
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.
### 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());
}
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
self.scope.set_value("state2", SomeType::new(42));
// Turn function-not-found into a success
Ok(().into())
Ok(Dynamic::UNIT)
}
_ => Err(err.into())
})

View File

@ -131,7 +131,7 @@ pub mod bunny_api {
Err("Bunny is not yet going!".into())
} else {
b.borrow_mut().set_speed(speed);
Ok(().into())
Ok(Dynamic::UNIT)
}
}
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_
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`
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'
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].
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
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
-------------------------------------

View File

@ -10,7 +10,7 @@ packages to be used.
Packages typically contain Rust functions that are callable within a Rhai script.
All functions registered in a package is loaded under the _global namespace_
(i.e. they're available without module qualifiers).
(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`)
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
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_
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.

View File

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

View File

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

View File

@ -1,9 +1,9 @@
//! 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::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::optimize::OptimizationLevel;
use crate::packages::{Package, PackagesCollection, StandardPackage};
@ -12,29 +12,11 @@ use crate::result::EvalAltResult;
use crate::scope::{EntryType as ScopeEntryType, Scope};
use crate::syntax::CustomSyntax;
use crate::token::{Position, NO_POS};
use crate::utils::get_hasher;
use crate::utils::{get_hasher, ImmutableString};
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::{
any::type_name,
any::{type_name, TypeId},
borrow::Cow,
boxed::Box,
collections::{HashMap, HashSet},
@ -44,20 +26,16 @@ use crate::stdlib::{
num::NonZeroUsize,
ops::DerefMut,
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.
///
/// Not available under the `no_index` feature.
#[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.
///
@ -65,6 +43,9 @@ pub type Array = Vec<Dynamic>;
#[cfg(not(feature = "no_object"))]
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.
/// 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
// the AST of the eval script text. The best we can do is a shared reference.
#[derive(Debug, Clone, Default)]
pub struct Imports(StaticVec<(ImmutableString, Shared<Module>)>);
pub struct Imports(StaticVec<(ImmutableString, bool, Shared<Module>)>);
impl Imports {
/// Get the length of this stack of imported modules.
@ -90,7 +71,7 @@ impl Imports {
}
/// Get the imported module at a particular index.
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.
pub fn find(&self, name: &str) -> Option<usize> {
@ -98,12 +79,21 @@ impl Imports {
.iter()
.enumerate()
.rev()
.find(|(_, (key, _))| key.as_str() == name)
.find(|(_, (key, _, _))| key.as_str() == name)
.map(|(index, _)| index)
}
/// Push an imported module onto the stack.
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.
pub fn truncate(&mut self, size: usize) {
@ -111,26 +101,59 @@ impl Imports {
}
/// Get an iterator to this stack of imported modules.
#[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
.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.
#[allow(dead_code)]
pub(crate) fn iter_raw<'a>(
&'a self,
) -> impl Iterator<Item = (ImmutableString, Shared<Module>)> + 'a {
) -> impl Iterator<Item = (ImmutableString, bool, Shared<Module>)> + 'a {
self.0.iter().cloned()
}
/// 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()
}
/// 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)
}
/// 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"))]
@ -178,6 +201,7 @@ pub const FN_IDX_GET: &str = "index$get$";
pub const FN_IDX_SET: &str = "index$set$";
#[cfg(not(feature = "no_function"))]
pub const FN_ANONYMOUS: &str = "anon$";
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
pub const OP_EQUALS: &str = "==";
pub const MARKER_EXPR: &str = "$expr$";
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.
#[cfg(not(feature = "no_closure"))]
#[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).
Value(Dynamic),
/// The target is a character inside a String.
@ -338,18 +362,10 @@ impl<'a> Target<'a> {
_ => None,
}
}
/// Get a mutable reference from the `Target`.
/// Convert a shared or reference `Target` into a target with an owned value.
#[inline(always)]
pub 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,
}
pub fn into_owned(self) -> Target<'static> {
self.take_or_clone().into()
}
/// Propagate a changed value back to the original source.
/// 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<'_> {
#[inline(always)]
fn from(value: T) -> Self {
@ -566,17 +612,19 @@ pub struct Engine {
pub(crate) global_module: Module,
/// A collection of all library packages loaded into the Engine.
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.
#[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.
pub(crate) type_names: HashMap<String, String>,
/// A hashset containing symbols to disable.
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>>,
/// Custom syntax.
pub(crate) custom_syntax: HashMap<ImmutableString, CustomSyntax>,
@ -681,11 +729,12 @@ impl Engine {
packages: Default::default(),
global_module: Default::default(),
global_sub_modules: Default::default(),
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_std"))]
#[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(any(feature = "no_std", target_arch = "wasm32",))]
module_resolver: None,
@ -743,6 +792,7 @@ impl Engine {
packages: Default::default(),
global_module: Default::default(),
global_sub_modules: Default::default(),
#[cfg(not(feature = "no_module"))]
module_resolver: None,
@ -919,6 +969,8 @@ impl Engine {
// Pop the last index value
let idx_val = idx_values.pop().unwrap();
let target_val = target.as_mut();
match chain_type {
#[cfg(not(feature = "no_index"))]
ChainType::Index => {
@ -930,7 +982,8 @@ impl Engine {
let idx_pos = x.lhs.position();
let idx_val = idx_val.as_value();
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(
@ -946,7 +999,7 @@ impl Engine {
// `call_setter` is introduced to bypass double mutable borrowing of target
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
Ok(ref mut obj_ptr) => {
@ -964,9 +1017,8 @@ impl Engine {
#[cfg(not(feature = "no_index"))]
if let Some(mut new_val) = _call_setter {
let val = target.as_mut();
let val_type_name = val.type_name();
let args = &mut [val, &mut idx_val2, &mut new_val.0];
let val_type_name = target_val.type_name();
let args = &mut [target_val, &mut idx_val2, &mut new_val.0];
self.exec_fn_call(
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();
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))
}
@ -1021,22 +1073,22 @@ impl Engine {
// xxx.module::fn_name(...) - syntax error
Expr::FnCall(_, _) => unreachable!(),
// {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 index = name.clone().into();
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())?;
Ok((Default::default(), true))
}
// {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 index = name.clone().into();
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))
@ -1045,7 +1097,7 @@ impl Engine {
Expr::Property(x) if new_val.is_some() => {
let ((_, setter), IdentX { pos, .. }) = x.as_ref();
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(
mods, state, lib, setter, 0, &mut args, is_ref, true, false, None,
None, level,
@ -1056,7 +1108,7 @@ impl Engine {
// xxx.id
Expr::Property(x) => {
let ((getter, _), IdentX { pos, .. }) = x.as_ref();
let mut args = [target.as_mut()];
let mut args = [target_val];
self.exec_fn_call(
mods, state, lib, getter, 0, &mut args, is_ref, true, false, None,
None, level,
@ -1065,13 +1117,14 @@ impl Engine {
.map_err(|err| err.fill_position(*pos))
}
// {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 {
Expr::Property(p) => {
let IdentX { name, pos } = &p.1;
let index = name.clone().into();
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
@ -1111,7 +1164,7 @@ impl Engine {
// xxx.prop[expr] | xxx.prop.expr
Expr::Property(p) => {
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 (mut val, updated) = self
.exec_fn_call(
@ -1212,7 +1265,7 @@ impl Engine {
level: usize,
new_val: Option<(Dynamic, Position)>,
) -> 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::Dot(x, pos) => (x.as_ref(), ChainType::Dot, *pos),
_ => unreachable!(),
@ -1314,7 +1367,7 @@ impl Engine {
Expr::FnCall(_, _) => unreachable!(),
Expr::Property(_) => idx_values.push(IndexChainValue::None),
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
let lhs_val = match lhs {
@ -1358,32 +1411,28 @@ impl Engine {
/// Get the value at the indexed position of a base type
/// Position in `EvalAltResult` may be None and should be set afterwards.
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
fn get_indexed_mut<'a>(
fn get_indexed_mut<'t>(
&self,
_mods: &mut Imports,
state: &mut State,
_lib: &[&Module],
target: &'a mut Target,
target: &'t mut Dynamic,
idx: Dynamic,
idx_pos: Position,
_create: bool,
_is_ref: bool,
_indexers: bool,
_level: usize,
) -> Result<Target<'a>, Box<EvalAltResult>> {
) -> Result<Target<'t>, Box<EvalAltResult>> {
self.inc_operations(state)?;
#[cfg(not(feature = "no_index"))]
let is_ref = target.is_ref();
let val = target.as_mut();
match val {
match target {
#[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(arr)) => {
// val_array[idx]
let index = idx
.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();
@ -1424,14 +1473,14 @@ impl Engine {
let chars_len = s.chars().count();
let index = idx
.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 {
let offset = index as usize;
let ch = s.chars().nth(offset).ok_or_else(|| {
EvalAltResult::ErrorStringBounds(chars_len, index, idx_pos)
})?;
Ok(Target::StringChar(val, offset, ch.into()))
Ok(Target::StringChar(target, offset, ch.into()))
} else {
EvalAltResult::ErrorStringBounds(chars_len, index, idx_pos).into()
}
@ -1439,11 +1488,11 @@ impl Engine {
#[cfg(not(feature = "no_index"))]
_ if _indexers => {
let type_name = val.type_name();
let type_name = target.type_name();
let mut idx = idx;
let args = &mut [val, &mut idx];
let args = &mut [target, &mut idx];
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,
)
.map(|(v, _)| v.into())
@ -1455,10 +1504,11 @@ impl Engine {
})
}
_ => {
EvalAltResult::ErrorIndexingType(self.map_type_name(val.type_name()).into(), NO_POS)
.into()
}
_ => EvalAltResult::ErrorIndexingType(
self.map_type_name(target.type_name()).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
pub(crate) fn eval_expr(
&self,
@ -1543,6 +1706,7 @@ impl Engine {
let result = match expr {
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()),
#[cfg(not(feature = "no_float"))]
Expr::FloatConstant(x, _) => Ok((*x).into()),
@ -1581,21 +1745,27 @@ impl Engine {
}
#[cfg(not(feature = "no_index"))]
Expr::Array(x, _) => Ok(Dynamic(Union::Array(Box::new(
x.iter()
.map(|item| self.eval_expr(scope, mods, state, lib, this_ptr, item, level))
.collect::<Result<Vec<_>, _>>()?,
)))),
Expr::Array(x, _) => {
let mut arr =
Array::with_capacity(crate::stdlib::cmp::max(TYPICAL_ARRAY_SIZE, x.len()));
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"))]
Expr::Map(x, _) => Ok(Dynamic(Union::Map(Box::new(
x.iter()
.map(|(key, expr)| {
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
.map(|val| (key.name.clone(), val))
})
.collect::<Result<HashMap<_, _>, _>>()?,
)))),
Expr::Map(x, _) => {
let mut map =
Map::with_capacity(crate::stdlib::cmp::max(TYPICAL_MAP_SIZE, x.len()));
for (IdentX { name: key, .. }, expr) in x.as_ref() {
map.insert(
key.clone(),
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?,
);
}
Ok(Dynamic(Union::Map(Box::new(map))))
}
// Normal function call
Expr::FnCall(x, pos) if x.namespace.is_none() => {
@ -1666,26 +1836,7 @@ impl Engine {
Expr::True(_) => Ok(true.into()),
Expr::False(_) => Ok(false.into()),
Expr::Unit(_) => Ok(().into()),
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::Unit(_) => Ok(Dynamic::UNIT),
Expr::Custom(custom, _) => {
let expressions = custom
@ -1814,7 +1965,8 @@ impl Engine {
match self
.global_module
.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
Some(func) if func.is_method() => {
@ -1832,9 +1984,10 @@ impl Engine {
// Overriding exact implementation
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 {
func.get_native_fn()((self, mods, lib).into(), args)?;
func.get_native_fn()((self, &*mods, lib).into(), args)?;
}
}
// Built-in op-assignment function
@ -1926,8 +2079,8 @@ impl Engine {
self.eval_statements(scope, mods, state, lib, this_ptr, statements, level)
}
// If-else statement
Stmt::IfThenElse(expr, x, _) => {
// If statement
Stmt::If(expr, x, _) => {
let (if_block, else_block) = x.as_ref();
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.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
Stmt::While(expr, body, _) => loop {
match self
@ -1987,7 +2162,8 @@ impl Engine {
let func = self
.global_module
.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 {
// Add the loop variable
@ -2094,25 +2270,25 @@ impl Engine {
}
// 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)?,
*pos,
)
.into(),
// Empty return
Stmt::ReturnWithVal((ReturnType::Return, pos), None, _) => {
Stmt::Return((ReturnType::Return, pos), None, _) => {
EvalAltResult::Return(Default::default(), *pos).into()
}
// 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)?;
EvalAltResult::ErrorRuntime(val, *pos).into()
}
// Empty throw
Stmt::ReturnWithVal((ReturnType::Exception, pos), None, _) => {
Stmt::Return((ReturnType::Exception, pos), None, _) => {
EvalAltResult::ErrorRuntime(().into(), *pos).into()
}
@ -2172,7 +2348,7 @@ impl Engine {
if let Some(name_def) = alias {
if !module.is_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();
mods.push(name_def.name.clone(), module);
} else {
@ -2219,7 +2395,7 @@ impl Engine {
if !val.is_shared() {
// 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::result::EvalAltResult;
use crate::scope::Scope;
use crate::token::{Position, NO_POS};
use crate::token::NO_POS;
use crate::utils::get_hasher;
#[cfg(not(feature = "no_index"))]
use crate::{
engine::{Array, FN_IDX_GET, FN_IDX_SET},
utils::ImmutableString,
};
use crate::Array;
#[cfg(not(feature = "no_object"))]
use crate::{
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::Map;
use crate::stdlib::{
any::{type_name, TypeId},
@ -40,9 +24,6 @@ use crate::stdlib::{
string::String,
};
#[cfg(not(feature = "no_optimize"))]
use crate::stdlib::mem;
#[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
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
}
/// Register a custom type for use with the `Engine`.
/// The type must implement `Clone`.
///
@ -126,7 +106,6 @@ impl Engine {
pub fn register_type<T: Variant + Clone>(&mut self) -> &mut Self {
self.register_type_with_name::<T>(type_name::<T>())
}
/// Register a custom type for use with the `Engine`, with a pretty-print name
/// 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
}
/// Register an iterator adapter for an iterable type with the `Engine`.
/// This is an advanced feature.
#[inline(always)]
@ -189,7 +167,6 @@ impl Engine {
self.global_module.set_iterable::<T>();
self
}
/// 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`.
@ -236,9 +213,8 @@ impl Engine {
T: 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`.
/// Returns `Result<Dynamic, Box<EvalAltResult>>`.
///
@ -284,9 +260,12 @@ impl Engine {
name: &str,
callback: impl Fn(&mut T) -> Result<Dynamic, Box<EvalAltResult>> + SendSync + 'static,
) -> &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`.
///
/// # Example
@ -334,9 +313,8 @@ impl Engine {
T: 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`.
/// Returns `Result<(), Box<EvalAltResult>>`.
///
@ -388,11 +366,12 @@ impl Engine {
T: Variant + Clone,
U: Variant + Clone,
{
self.register_result_fn(&make_setter(name), move |obj: &mut T, value: U| {
callback(obj, value).map(Into::into)
})
crate::RegisterResultFn::register_result_fn(
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
/// of a registered type with the `Engine`.
///
@ -445,7 +424,6 @@ impl Engine {
{
self.register_get(name, get_fn).register_set(name, set_fn)
}
/// Register an index getter for a custom type with the `Engine`.
///
/// The function signature must start with `&mut self` and not `&self`.
@ -507,14 +485,13 @@ impl Engine {
}
if TypeId::of::<T>() == TypeId::of::<String>()
|| 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.");
}
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`.
/// Returns `Result<Dynamic, Box<EvalAltResult>>`.
///
@ -578,14 +555,13 @@ impl Engine {
}
if TypeId::of::<T>() == TypeId::of::<String>()
|| 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.");
}
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`.
///
/// # Panics
@ -647,14 +623,13 @@ impl Engine {
}
if TypeId::of::<T>() == TypeId::of::<String>()
|| 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.");
}
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`.
/// Returns `Result<(), Box<EvalAltResult>>`.
///
@ -720,16 +695,17 @@ impl Engine {
}
if TypeId::of::<T>() == TypeId::of::<String>()
|| 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.");
}
self.register_result_fn(FN_IDX_SET, move |obj: &mut T, index: X, value: U| {
callback(obj, index, value).map(Into::into)
})
crate::RegisterResultFn::register_result_fn(
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`.
///
/// # Panics
@ -785,7 +761,45 @@ impl Engine {
self.register_indexer_get(getter)
.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.
///
/// # Example
@ -809,7 +823,6 @@ impl Engine {
pub fn compile(&self, script: &str) -> Result<AST, ParseError> {
self.compile_with_scope(&Default::default(), script)
}
/// 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
@ -852,7 +865,6 @@ impl Engine {
pub fn compile_with_scope(&self, scope: &Scope, script: &str) -> Result<AST, ParseError> {
self.compile_scripts_with_scope(scope, &[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.
///
@ -907,7 +919,6 @@ impl Engine {
) -> Result<AST, ParseError> {
self.compile_with_scope_and_optimization_level(scope, scripts, self.optimization_level)
}
/// Join a list of strings and compile into an `AST` using own scope at a specific optimization level.
#[inline(always)]
pub(crate) fn compile_with_scope_and_optimization_level(
@ -920,7 +931,6 @@ impl Engine {
let stream = self.lex(scripts, None);
self.parse(hash, &mut stream.peekable(), scope, optimization_level)
}
/// Read the contents of a file into a string.
#[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
@ -944,7 +954,6 @@ impl Engine {
Ok(contents)
}
/// Compile a script file into an `AST`, which can be used later for evaluation.
///
/// # Example
@ -971,7 +980,6 @@ impl Engine {
pub fn compile_file(&self, path: PathBuf) -> Result<AST, Box<EvalAltResult>> {
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.
///
/// The scope is useful for passing constants into the script for optimization
@ -1013,7 +1021,6 @@ impl Engine {
) -> Result<AST, Box<EvalAltResult>> {
Self::read_file(path).and_then(|contents| Ok(self.compile_with_scope(scope, &contents)?))
}
/// Parse a JSON string into a map.
///
/// 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"))]
pub fn parse_json(&self, json: &str, has_null: bool) -> Result<Map, Box<EvalAltResult>> {
use crate::token::{Position, Token};
let mut scope = Default::default();
// 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()) {
["#", json_text]
} else {
return Err(ParseErrorType::MissingToken(
return Err(crate::ParseErrorType::MissingToken(
Token::LeftBrace.syntax().into(),
"to start a JSON object hash".into(),
)
@ -1098,7 +1107,6 @@ impl Engine {
self.eval_ast_with_scope(&mut scope, &ast)
}
/// Compile a string containing an expression into an `AST`,
/// which can be used later for evaluation.
///
@ -1123,7 +1131,6 @@ impl Engine {
pub fn compile_expression(&self, script: &str) -> Result<AST, ParseError> {
self.compile_expression_with_scope(&Default::default(), script)
}
/// Compile a string containing an expression into an `AST` using own scope,
/// which can be used later for evaluation.
///
@ -1176,7 +1183,6 @@ impl Engine {
let mut peekable = stream.peekable();
self.parse_global_expr(hash, &mut peekable, scope, self.optimization_level)
}
/// Evaluate a script file.
///
/// # Example
@ -1198,7 +1204,6 @@ impl Engine {
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))
}
/// Evaluate a script file with own scope.
///
/// # Example
@ -1228,7 +1233,6 @@ impl Engine {
) -> Result<T, Box<EvalAltResult>> {
Self::read_file(path).and_then(|contents| self.eval_with_scope::<T>(scope, &contents))
}
/// Evaluate a string.
///
/// # Example
@ -1247,7 +1251,6 @@ impl Engine {
pub fn eval<T: Variant + Clone>(&self, script: &str) -> Result<T, Box<EvalAltResult>> {
self.eval_with_scope(&mut Default::default(), script)
}
/// Evaluate a string with own scope.
///
/// # Example
@ -1283,7 +1286,6 @@ impl Engine {
)?;
self.eval_ast_with_scope(scope, &ast)
}
/// Evaluate a string containing an expression.
///
/// # Example
@ -1305,7 +1307,6 @@ impl Engine {
) -> Result<T, Box<EvalAltResult>> {
self.eval_expression_with_scope(&mut Default::default(), script)
}
/// Evaluate a string containing an expression with own scope.
///
/// # Example
@ -1340,7 +1341,6 @@ impl Engine {
self.eval_ast_with_scope(scope, &ast)
}
/// Evaluate an `AST`.
///
/// # Example
@ -1363,7 +1363,6 @@ impl Engine {
pub fn eval_ast<T: Variant + Clone>(&self, ast: &AST) -> Result<T, Box<EvalAltResult>> {
self.eval_ast_with_scope(&mut Default::default(), ast)
}
/// Evaluate an `AST` with own scope.
///
/// # Example
@ -1399,7 +1398,8 @@ impl Engine {
scope: &mut Scope,
ast: &AST,
) -> 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 typ = self.map_type_name(result.type_name());
@ -1413,7 +1413,6 @@ impl Engine {
.into()
});
}
/// Evaluate an `AST` with own scope.
#[inline(always)]
pub(crate) fn eval_ast_with_scope_raw<'a>(
@ -1424,7 +1423,6 @@ impl Engine {
) -> Result<(Dynamic, u64), Box<EvalAltResult>> {
self.eval_statements_raw(scope, mods, ast.statements(), &[ast.lib()])
}
/// 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.
#[cfg(not(feature = "no_std"))]
@ -1433,7 +1431,6 @@ impl Engine {
pub fn consume_file(&self, path: PathBuf) -> Result<(), Box<EvalAltResult>> {
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).
/// Useful for when you don't need the result, but still need to keep track of possible errors.
#[cfg(not(feature = "no_std"))]
@ -1446,14 +1443,12 @@ impl Engine {
) -> Result<(), Box<EvalAltResult>> {
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).
/// Useful for when you don't need the result, but still need to keep track of possible errors.
#[inline(always)]
pub fn consume(&self, script: &str) -> Result<(), Box<EvalAltResult>> {
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).
/// Useful for when you don't need the result, but still need to keep track of possible errors.
#[inline]
@ -1468,14 +1463,12 @@ impl Engine {
let ast = self.parse(hash, &mut stream.peekable(), scope, self.optimization_level)?;
self.consume_ast_with_scope(scope, &ast)
}
/// 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.
#[inline(always)]
pub fn consume_ast(&self, ast: &AST) -> Result<(), Box<EvalAltResult>> {
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).
/// Useful for when you don't need the result, but still need to keep track of possible errors.
#[inline(always)]
@ -1484,11 +1477,11 @@ impl Engine {
scope: &mut Scope,
ast: &AST,
) -> 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()])
.map(|_| ())
}
/// Call a script function defined in an `AST` with multiple arguments.
/// Arguments are passed as a tuple.
///
@ -1527,7 +1520,7 @@ impl Engine {
/// ```
#[cfg(not(feature = "no_function"))]
#[inline]
pub fn call_fn<A: FuncArgs, T: Variant + Clone>(
pub fn call_fn<A: crate::fn_args::FuncArgs, T: Variant + Clone>(
&self,
scope: &mut Scope,
ast: &AST,
@ -1535,7 +1528,7 @@ impl Engine {
args: A,
) -> Result<T, Box<EvalAltResult>> {
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 =
self.call_fn_dynamic_raw(scope, &[ast.lib()], name, &mut None, args.as_mut())?;
@ -1551,7 +1544,6 @@ impl Engine {
.into()
});
}
/// Call a script function defined in an `AST` with multiple `Dynamic` arguments
/// and optionally a value for binding to the 'this' pointer.
///
@ -1606,16 +1598,15 @@ impl Engine {
pub fn call_fn_dynamic(
&self,
scope: &mut Scope,
lib: impl AsRef<Module>,
lib: impl AsRef<crate::Module>,
name: &str,
mut this_ptr: Option<&mut Dynamic>,
mut arg_values: impl AsMut<[Dynamic]>,
) -> 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())
}
/// Call a script function defined in an `AST` with multiple `Dynamic` arguments.
///
/// ## WARNING
@ -1629,7 +1620,7 @@ impl Engine {
pub(crate) fn call_fn_dynamic_raw(
&self,
scope: &mut Scope,
lib: &[&Module],
lib: &[&crate::Module],
name: &str,
this_ptr: &mut Option<&mut Dynamic>,
args: &mut FnCallArgs,
@ -1640,16 +1631,15 @@ impl Engine {
.ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), NO_POS))?;
let mut state = Default::default();
let mut mods = Default::default();
let mut mods = self.global_sub_modules.clone();
// Check for data race.
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)
}
/// Optimize the `AST` with constants defined in an external Scope.
/// An optimized copy of the `AST` is returned while the original `AST` is consumed.
///
@ -1680,10 +1670,9 @@ impl Engine {
#[cfg(feature = "no_function")]
let lib = Default::default();
let stmt = mem::take(ast.statements_mut());
optimize_into_ast(self, scope, stmt, lib, optimization_level)
let stmt = crate::stdlib::mem::take(ast.statements_mut());
crate::optimize::optimize_into_ast(self, scope, stmt, lib, optimization_level)
}
/// Provide a callback that will be invoked before each variable access.
///
/// ## Return Value of Callback
@ -1726,7 +1715,6 @@ impl Engine {
self.resolve_var = Some(Box::new(callback));
self
}
/// Register a callback for script evaluation progress.
///
/// # Example
@ -1769,7 +1757,6 @@ impl Engine {
self.progress = Some(Box::new(callback));
self
}
/// Override default action of `print` (print to stdout using `println!`)
///
/// # Example
@ -1799,7 +1786,6 @@ impl Engine {
self.print = Box::new(callback);
self
}
/// Override default action of `debug` (print to stdout using `println!`)
///
/// # Example

View File

@ -4,12 +4,6 @@ use crate::engine::Engine;
use crate::packages::PackageLibrary;
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};
#[cfg(not(feature = "no_module"))]
@ -23,31 +17,30 @@ impl Engine {
/// In other words, loaded packages are searched in reverse order.
#[inline(always)]
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.push(package.into());
self.packages.add(package.into());
self
}
/// Control whether and how the `Engine` will optimize an AST after compilation.
///
/// Not available under the `no_optimize` feature.
#[cfg(not(feature = "no_optimize"))]
#[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
}
/// The current optimization level.
/// It controls whether and how the `Engine` will optimize an AST after compilation.
///
/// Not available under the `no_optimize` feature.
#[cfg(not(feature = "no_optimize"))]
#[inline(always)]
pub fn optimization_level(&self) -> OptimizationLevel {
pub fn optimization_level(&self) -> crate::OptimizationLevel {
self.optimization_level
}
/// Set the maximum levels of function calls allowed for a script in order to avoid
/// infinite recursion and stack overflows.
#[cfg(not(feature = "unchecked"))]
@ -56,14 +49,12 @@ impl Engine {
self.limits.max_call_stack_depth = levels;
self
}
/// The maximum levels of function calls allowed for a script.
#[cfg(not(feature = "unchecked"))]
#[inline(always)]
pub fn max_call_levels(&self) -> usize {
self.limits.max_call_stack_depth
}
/// Set the maximum number of operations allowed for a script to run to avoid
/// consuming too much resources (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
@ -76,14 +67,12 @@ impl Engine {
};
self
}
/// The maximum number of operations allowed for a script to run (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
#[inline(always)]
pub fn max_operations(&self) -> u64 {
self.limits.max_operations
}
/// Set the maximum number of imported modules allowed for a script.
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_module"))]
@ -92,7 +81,6 @@ impl Engine {
self.limits.max_modules = modules;
self
}
/// The maximum number of imported modules allowed for a script.
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_module"))]
@ -100,7 +88,6 @@ impl Engine {
pub fn max_modules(&self) -> usize {
self.limits.max_modules
}
/// Set the depth limits for expressions (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
#[inline(always)]
@ -124,14 +111,12 @@ impl Engine {
}
self
}
/// The depth limit for expressions (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
#[inline(always)]
pub fn max_expr_depth(&self) -> usize {
self.limits.max_expr_depth
}
/// The depth limit for expressions in functions (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_function"))]
@ -139,7 +124,6 @@ impl Engine {
pub fn max_function_expr_depth(&self) -> usize {
self.limits.max_function_expr_depth
}
/// Set the maximum length of strings (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
#[inline(always)]
@ -147,14 +131,12 @@ impl Engine {
self.limits.max_string_size = if max_size == usize::MAX { 0 } else { max_size };
self
}
/// The maximum length of strings (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
#[inline(always)]
pub fn max_string_size(&self) -> usize {
self.limits.max_string_size
}
/// Set the maximum length of arrays (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
#[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
}
/// The maximum length of arrays (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_index"))]
@ -171,7 +152,6 @@ impl Engine {
pub fn max_array_size(&self) -> usize {
self.limits.max_array_size
}
/// Set the maximum length of object maps (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
#[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
}
/// The maximum length of object maps (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_object"))]
@ -188,7 +167,6 @@ impl Engine {
pub fn max_map_size(&self) -> usize {
self.limits.max_map_size
}
/// Set the module resolution service used by the `Engine`.
///
/// Not available under the `no_module` feature.
@ -196,12 +174,11 @@ impl Engine {
#[inline(always)]
pub fn set_module_resolver(
&mut self,
resolver: Option<impl ModuleResolver + 'static>,
resolver: Option<impl crate::ModuleResolver + 'static>,
) -> &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
}
/// Disable a particular keyword or operator in the language.
///
/// # Examples
@ -243,7 +220,6 @@ impl Engine {
self.disabled_symbols.insert(symbol.into());
self
}
/// Register a custom operator into the language.
///
/// 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::parse_error::ParseErrorType;
use crate::result::EvalAltResult;
use crate::scope::Scope;
use crate::scope::{EntryType as ScopeEntryType, Scope};
use crate::stdlib::ops::Deref;
use crate::token::NO_POS;
use crate::utils::ImmutableString;
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"))]
use crate::FLOAT;
#[cfg(not(feature = "no_index"))]
use crate::engine::{FN_IDX_GET, FN_IDX_SET};
#[cfg(not(feature = "no_object"))]
use crate::engine::{Map, Target, FN_GET, FN_SET};
#[cfg(not(feature = "no_closure"))]
use crate::engine::KEYWORD_IS_SHARED;
use crate::Map;
use crate::stdlib::{
any::{type_name, TypeId},
@ -47,9 +35,6 @@ use crate::stdlib::{
vec::Vec,
};
#[cfg(not(feature = "no_function"))]
use crate::stdlib::borrow::Cow;
#[cfg(feature = "no_std")]
#[cfg(not(feature = "no_float"))]
use num_traits::float::Float;
@ -58,8 +43,8 @@ use num_traits::float::Float;
#[inline(always)]
fn extract_prop_from_getter(_fn_name: &str) -> Option<&str> {
#[cfg(not(feature = "no_object"))]
if _fn_name.starts_with(FN_GET) {
return Some(&_fn_name[FN_GET.len()..]);
if _fn_name.starts_with(crate::engine::FN_GET) {
return Some(&_fn_name[crate::engine::FN_GET.len()..]);
}
None
@ -69,8 +54,8 @@ fn extract_prop_from_getter(_fn_name: &str) -> Option<&str> {
#[inline(always)]
fn extract_prop_from_setter(_fn_name: &str) -> Option<&str> {
#[cfg(not(feature = "no_object"))]
if _fn_name.starts_with(FN_SET) {
return Some(&_fn_name[FN_SET.len()..]);
if _fn_name.starts_with(crate::engine::FN_SET) {
return Some(&_fn_name[crate::engine::FN_SET.len()..]);
}
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 `()`!
pub(crate) fn call_native_fn(
&self,
mods: &mut Imports,
mods: &Imports,
state: &mut State,
lib: &[&Module],
fn_name: &str,
@ -195,7 +180,8 @@ impl Engine {
// Then search packages
let func = //lib.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 {
assert!(func.is_native());
@ -286,7 +272,7 @@ impl Engine {
// index getter function not found?
#[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(
format!(
"{} [{}]",
@ -300,7 +286,7 @@ impl Engine {
// index setter function not found?
#[cfg(not(feature = "no_index"))]
if fn_name == FN_IDX_SET {
if fn_name == crate::engine::FN_IDX_SET {
return EvalAltResult::ErrorFunctionNotFound(
format!(
"{} [{}]=",
@ -347,7 +333,7 @@ impl Engine {
state: &mut State,
lib: &[&Module],
this_ptr: &mut Option<&mut Dynamic>,
fn_def: &ScriptFnDef,
fn_def: &crate::ast::ScriptFnDef,
args: &mut FnCallArgs,
level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> {
@ -374,7 +360,8 @@ impl Engine {
.iter()
.zip(args.iter_mut().map(|v| mem::take(*v)))
.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)
}),
);
@ -431,6 +418,7 @@ impl Engine {
#[inline]
pub(crate) fn has_override_by_name_and_arguments(
&self,
mods: &Imports,
lib: &[&Module],
fn_name: &str,
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_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?
#[inline(always)]
pub(crate) fn has_override(
&self,
mods: &Imports,
lib: &[&Module],
hash_fn: u64,
hash_script: u64,
@ -459,10 +448,13 @@ impl Engine {
//|| lib.iter().any(|&m| m.contains_fn(hash_fn, pub_only))
// Then check registered functions
//|| 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
|| self.packages.contains_fn(hash_script, pub_only)
|| self.packages.contains_fn(hash_fn, pub_only)
|| self.packages.contains_fn(hash_script)
|| 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.
@ -500,7 +492,8 @@ impl Engine {
match fn_name {
// 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((
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
// by a function pointer so it isn't caught at parse time.
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(
format!(
@ -526,16 +520,14 @@ impl Engine {
// Script-like function found
#[cfg(not(feature = "no_function"))]
_ if lib.iter().any(|&m| m.contains_fn(hash_script, pub_only))
//|| self.global_module.contains_fn(hash_script, pub_only)
|| self.packages.contains_fn(hash_script, pub_only) =>
{
_ if self.has_override(mods, lib, 0, hash_script, pub_only) => {
// Get function
let func = lib
.iter()
.find_map(|&m| m.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();
if func.is_script() {
@ -654,7 +646,7 @@ impl Engine {
let script = script.trim();
if script.is_empty() {
return Ok(().into());
return Ok(Dynamic::UNIT);
}
// Check for stack overflow
@ -696,7 +688,7 @@ impl Engine {
lib: &[&Module],
fn_name: &str,
hash_script: u64,
target: &mut Target,
target: &mut crate::engine::Target,
mut call_args: StaticVec<Dynamic>,
def_val: Option<Dynamic>,
native: bool,
@ -779,7 +771,7 @@ impl Engine {
} else if {
#[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")]
false
@ -863,7 +855,7 @@ impl Engine {
let hash_fn =
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
return self
.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?
@ -905,7 +897,7 @@ impl Engine {
// Handle is_shared()
#[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)?;
return Ok(value.is_shared().into());
@ -919,7 +911,7 @@ impl Engine {
if name == KEYWORD_FN_PTR_CALL
&& 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)?;
@ -949,7 +941,7 @@ impl Engine {
if name == KEYWORD_IS_DEF_VAR && args_expr.len() == 1 {
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 =
self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?;
let var_name = var_name.as_str().map_err(|err| {
@ -969,7 +961,7 @@ impl Engine {
.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 =
self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?;
let num_params =
@ -996,7 +988,7 @@ impl Engine {
if name == KEYWORD_EVAL && args_expr.len() == 1 {
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
let prev_len = scope.len();
let script =
@ -1042,9 +1034,14 @@ impl Engine {
.map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level))
.collect::<Result<_, _>>()?;
let (target, _, _, pos) =
let (target, _, typ, pos) =
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)
.map_err(|err| err.fill_position(pos))?;
@ -1192,7 +1189,7 @@ impl Engine {
Some(f) if f.is_plugin_fn() => f
.get_plugin_fn()
.clone()
.call((self, mods, lib).into(), args.as_mut()),
.call((self, &*mods, lib).into(), args.as_mut()),
Some(f) if f.is_native() => {
if !f.is_method() {
// 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!(),
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::{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};
#[cfg(feature = "sync")]
use crate::stdlib::sync::{Arc, RwLock};
#[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.
#[cfg(feature = "sync")]
@ -42,9 +46,11 @@ pub type Shared<T> = Rc<T>;
pub type Shared<T> = Arc<T>;
/// Synchronized shared object.
#[cfg(any(not(feature = "no_closure"), not(feature = "no_module")))]
#[cfg(not(feature = "sync"))]
pub type Locked<T> = RefCell<T>;
/// Synchronized shared object.
#[cfg(any(not(feature = "no_closure"), not(feature = "no_module")))]
#[cfg(feature = "sync")]
pub type Locked<T> = RwLock<T>;
@ -56,10 +62,10 @@ pub struct NativeCallContext<'e, 'a, 'm, 'pm: 'm> {
lib: &'m [&'pm Module],
}
impl<'e, 'a, 'm, 'pm: 'm, M: AsRef<[&'pm Module]> + ?Sized>
From<(&'e Engine, &'a mut Imports, &'m M)> for NativeCallContext<'e, 'a, 'm, 'pm>
impl<'e, 'a, 'm, 'pm: 'm, M: AsRef<[&'pm Module]> + ?Sized> From<(&'e Engine, &'a Imports, &'m M)>
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 {
engine: value.0,
mods: Some(value.1),
@ -249,7 +255,7 @@ impl FnPtr {
#[cfg(not(feature = "no_function"))]
#[inline(always)]
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).
///

View File

@ -124,6 +124,9 @@ pub use syntax::Expression;
pub use token::{Position, NO_POS};
pub use utils::ImmutableString;
#[allow(dead_code)]
use fn_native::{Locked, Shared};
#[cfg(feature = "internals")]
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::token::{Token, NO_POS};
use crate::utils::{ImmutableString, StraightHasherBuilder};
use crate::{calc_native_fn_hash, calc_script_fn_hash, StaticVec};
#[cfg(not(feature = "no_function"))]
use crate::ast::ScriptFnDef;
#[cfg(not(feature = "no_module"))]
use crate::{ast::AST, engine::Engine, scope::Scope};
use crate::StaticVec;
#[cfg(not(feature = "no_index"))]
use crate::engine::{Array, FN_IDX_GET, FN_IDX_SET};
#[cfg(not(feature = "no_object"))]
use crate::engine::{make_getter, make_setter};
use crate::Array;
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))]
use crate::engine::Map;
use crate::Map;
use crate::stdlib::{
any::TypeId,
@ -59,7 +50,7 @@ pub struct FuncInfo {
/// and/or script-defined functions.
///
/// Not available under the `no_module` feature.
#[derive(Default, Clone)]
#[derive(Clone)]
pub struct Module {
/// Sub-modules.
modules: HashMap<String, Shared<Module>>,
@ -69,15 +60,32 @@ pub struct Module {
all_variables: HashMap<u64, Dynamic, StraightHasherBuilder>,
/// External Rust functions.
functions: HashMap<u64, FuncInfo, StraightHasherBuilder>,
/// Iterator functions, keyed by the type producing the iterator.
type_iterators: HashMap<TypeId, IteratorFn>,
/// Flattened collection of all external Rust functions, native or scripted,
/// Flattened collection of all external Rust functions, native or scripted.
/// including those in sub-modules.
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?
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 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
@ -163,6 +171,7 @@ impl Module {
&& self.all_variables.is_empty()
&& self.modules.is_empty()
&& self.type_iterators.is_empty()
&& self.all_type_iterators.is_empty()
}
/// 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.
#[cfg(not(feature = "no_function"))]
#[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.
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(
hash_script,
FuncInfo {
@ -299,7 +308,7 @@ impl Module {
name: &str,
num_params: usize,
public_only: bool,
) -> Option<&Shared<ScriptFnDef>> {
) -> Option<&Shared<crate::ast::ScriptFnDef>> {
self.functions
.values()
.find(
@ -437,7 +446,7 @@ impl Module {
) -> u64 {
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
.into_iter()
@ -656,7 +665,7 @@ impl Module {
name: impl Into<String>,
func: impl Fn(&mut A) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
) -> 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.
@ -756,7 +765,7 @@ impl Module {
name: impl Into<String>,
func: impl Fn(&mut A, B) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
) -> 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,
@ -800,7 +809,7 @@ impl Module {
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.
@ -939,7 +948,7 @@ impl Module {
};
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn(
FN_IDX_SET,
crate::engine::FN_IDX_SET,
FnAccess::Public,
&arg_types,
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.
/// 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.all_functions.clear();
self.all_variables.clear();
self.all_type_iterators.clear();
self.indexed = false;
self
}
@ -1142,6 +1161,7 @@ impl Module {
self.type_iterators.extend(other.type_iterators.into_iter());
self.all_functions.clear();
self.all_variables.clear();
self.all_type_iterators.clear();
self.indexed = false;
self
}
@ -1168,6 +1188,7 @@ impl Module {
});
self.all_functions.clear();
self.all_variables.clear();
self.all_type_iterators.clear();
self.indexed = false;
self
}
@ -1213,6 +1234,7 @@ impl Module {
self.type_iterators.extend(other.type_iterators.iter());
self.all_functions.clear();
self.all_variables.clear();
self.all_type_iterators.clear();
self.indexed = false;
self
}
@ -1232,6 +1254,7 @@ impl Module {
self.all_functions.clear();
self.all_variables.clear();
self.all_type_iterators.clear();
self.indexed = false;
self
}
@ -1271,7 +1294,7 @@ impl Module {
#[inline(always)]
pub(crate) fn iter_script_fn<'a>(
&'a self,
) -> impl Iterator<Item = (FnAccess, &str, usize, Shared<ScriptFnDef>)> + 'a {
) -> impl Iterator<Item = (FnAccess, &str, usize, Shared<crate::ast::ScriptFnDef>)> + 'a {
self.functions
.values()
.map(|f| &f.func)
@ -1316,7 +1339,7 @@ impl Module {
#[inline(always)]
pub fn iter_script_fn_info(
&self,
) -> impl Iterator<Item = (FnAccess, &str, usize, Shared<ScriptFnDef>)> {
) -> impl Iterator<Item = (FnAccess, &str, usize, Shared<crate::ast::ScriptFnDef>)> {
self.iter_script_fn()
}
@ -1343,11 +1366,12 @@ impl Module {
/// ```
#[cfg(not(feature = "no_module"))]
pub fn eval_ast_as_new(
mut scope: Scope,
ast: &AST,
engine: &Engine,
mut scope: crate::Scope,
ast: &crate::AST,
engine: &crate::Engine,
) -> 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
engine.eval_ast_with_scope_raw(&mut scope, &mut mods, &ast)?;
@ -1366,10 +1390,16 @@ impl Module {
}
});
// Modules left in the scope become sub-modules
mods.iter().for_each(|(alias, m)| {
module.set_sub_module(alias, m);
});
// Extra modules left in the scope become sub-modules
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);
});
// Non-private functions defined become module functions
#[cfg(not(feature = "no_function"))]
@ -1382,7 +1412,7 @@ impl Module {
// Encapsulate AST environment
let mut func = func.as_ref().clone();
func.lib = Some(ast_lib.clone());
func.mods = mods.clone();
func.mods = func_mods.clone();
module.set_script_fn(func.into());
});
}
@ -1400,22 +1430,30 @@ impl Module {
fn index_module<'a>(
module: &'a Module,
qualifiers: &mut Vec<&'a str>,
variables: &mut Vec<(u64, Dynamic)>,
functions: &mut Vec<(u64, CallableFunction)>,
variables: &mut HashMap<u64, Dynamic, StraightHasherBuilder>,
functions: &mut HashMap<u64, CallableFunction, StraightHasherBuilder>,
type_iterators: &mut HashMap<TypeId, IteratorFn>,
) {
module.modules.iter().for_each(|(name, m)| {
// Index all the sub-modules first.
qualifiers.push(name);
index_module(m, qualifiers, variables, functions);
index_module(m, qualifiers, variables, functions, type_iterators);
qualifiers.pop();
});
// Index all variables
module.variables.iter().for_each(|(var_name, value)| {
// Qualifiers + variable name
let hash_var = calc_script_fn_hash(qualifiers.iter().map(|&v| v), var_name, 0);
variables.push((hash_var, value.clone()));
let hash_var =
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
module
.functions
@ -1423,7 +1461,7 @@ impl Module {
.filter(|(_, FuncInfo { access, .. })| access.is_public())
.for_each(
|(
&_hash,
&hash,
FuncInfo {
name,
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 {
assert_eq!(*params, param_types.len());
// Namespace-qualified Rust functions are indexed in two steps:
// 1) Calculate a hash in a similar manner to script-defined functions,
// i.e. qualifiers + function name + number of arguments.
let hash_qualified_script =
calc_script_fn_hash(qualifiers.iter().cloned(), name, *params);
let hash_qualified_script = crate::calc_script_fn_hash(
qualifiers.iter().cloned(),
name,
*params,
);
// 2) Calculate a second hash with no qualifiers, empty function name,
// and the actual list of argument `TypeId`'.s
let hash_fn_args =
calc_native_fn_hash(empty(), "", param_types.iter().cloned());
let hash_fn_args = crate::calc_native_fn_hash(
empty(),
"",
param_types.iter().cloned(),
);
// 3) The final hash is the XOR of the two hashes.
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")) {
let hash_qualified_script = if qualifiers.is_empty() {
_hash
} else {
// Qualifiers + function name + number of arguments.
calc_script_fn_hash(qualifiers.iter().map(|&v| v), &name, *params)
};
functions.push((hash_qualified_script, func.clone()));
let hash_qualified_script =
if cfg!(feature = "no_object") && qualifiers.is_empty() {
hash
} else {
// Qualifiers + function name + number of arguments.
crate::calc_script_fn_hash(
qualifiers.iter().map(|&v| v),
&name,
*params,
)
};
functions.insert(hash_qualified_script, func.clone());
}
},
);
}
if !self.indexed {
let mut qualifiers: Vec<_> = Default::default();
let mut variables: Vec<_> = Default::default();
let mut functions: Vec<_> = Default::default();
let mut qualifiers = Vec::with_capacity(4);
let mut variables = HashMap::with_capacity_and_hasher(16, StraightHasherBuilder);
let mut functions = HashMap::with_capacity_and_hasher(256, StraightHasherBuilder);
let mut type_iterators = HashMap::with_capacity(16);
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_functions = functions.into_iter().collect();
self.all_variables = variables;
self.all_functions = functions;
self.all_type_iterators = type_iterators;
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?
pub fn contains_iter(&self, id: TypeId) -> bool {
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.
pub(crate) fn get_iter(&self, id: TypeId) -> Option<IteratorFn> {
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::{calc_native_fn_hash, StaticVec};
#[cfg(not(feature = "no_function"))]
use crate::ast::ReturnType;
use crate::stdlib::{
boxed::Box,
hash::{Hash, Hasher},
@ -237,7 +234,7 @@ fn optimize_stmt_block(
}
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);
}
},
// 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();
*stmt = Stmt::Noop(*pos);
}
// 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);
optimize_stmt(stmt, state, true);
}
// if expr { Noop }
Stmt::IfThenElse(ref mut condition, x, _)
if x.1.is_none() && matches!(x.0, Stmt::Noop(_)) =>
{
Stmt::If(ref mut condition, x, _) if x.1.is_none() && matches!(x.0, Stmt::Noop(_)) => {
state.set_dirty();
let pos = condition.position();
@ -317,22 +313,22 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
};
}
// 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_stmt(&mut x.0, state, true);
}
// 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());
optimize_stmt(stmt, state, true);
}
// 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);
optimize_stmt(stmt, state, true);
}
// 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_stmt(&mut x.0, state, true);
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
Stmt::While(Expr::False(pos), _, _) => {
state.set_dirty();
@ -435,7 +467,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
// expr;
Stmt::Expr(ref mut expr) => optimize_expr(expr, state),
// 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
_ => (),
@ -523,12 +555,24 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
// lhs[rhs]
(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 .. ]
#[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, .. }
#[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
Expr::In(x, _) => match (&mut x.lhs, &mut x.rhs) {
// "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();
// 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])
.ok().flatten()
.and_then(|result| map_dynamic_to_expr(result, *pos))
@ -697,42 +741,6 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
*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
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(
statements: Vec<Stmt>,
mut statements: Vec<Stmt>,
engine: &Engine,
scope: &Scope,
lib: &[&Module],
@ -750,6 +758,7 @@ fn optimize(
) -> Vec<Stmt> {
// If optimization level is None then skip optimizing
if level == OptimizationLevel::None {
statements.shrink_to_fit();
return statements;
}
@ -768,16 +777,14 @@ fn optimize(
let orig_constants_len = state.constants.len();
let mut result = statements;
// Optimization loop
loop {
state.reset();
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 {
Stmt::Const(var_def, expr, _, _) if expr.is_some() => {
// Load constants
@ -817,26 +824,27 @@ fn optimize(
}
// 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
result.retain(|stmt| !stmt.is_pure());
statements.retain(|stmt| !stmt.is_pure());
// Add back the last statement unless it is a lone No-op
if let Some(stmt) = last_stmt {
if !result.is_empty() || !stmt.is_noop() {
result.push(stmt);
if !statements.is_empty() || !stmt.is_noop() {
statements.push(stmt);
}
}
result
statements.shrink_to_fit();
statements
}
/// Optimize an AST.
pub fn optimize_into_ast(
engine: &Engine,
scope: &Scope,
statements: Vec<Stmt>,
mut statements: Vec<Stmt>,
_functions: Vec<ScriptFnDef>,
level: OptimizationLevel,
) -> AST {
@ -886,11 +894,11 @@ pub fn optimize_into_ast(
// {} -> Noop
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
// { return val; } -> val
Stmt::ReturnWithVal((ReturnType::Return, _), Some(expr), _) => {
Stmt::Return((crate::ast::ReturnType::Return, _), Some(expr), _) => {
Stmt::Expr(expr)
}
// { return; } -> ()
Stmt::ReturnWithVal((ReturnType::Return, pos), None, _) => {
Stmt::Return((crate::ast::ReturnType::Return, pos), None, _) => {
Stmt::Expr(Expr::Unit(pos))
}
// All others
@ -913,6 +921,8 @@ pub fn optimize_into_ast(
#[cfg(feature = "no_function")]
let lib = Default::default();
statements.shrink_to_fit();
AST::new(
match level {
OptimizationLevel::None => statements,

View File

@ -3,7 +3,7 @@
use crate::def_package;
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::plugin::*;
use crate::result::EvalAltResult;
@ -12,9 +12,9 @@ use crate::utils::ImmutableString;
use crate::INT;
#[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 = ();
@ -54,7 +54,7 @@ macro_rules! gen_array_functions {
list.resize(len as usize, Dynamic::from(item));
}
Ok(().into())
Ok(Dynamic::UNIT)
}
}
})* }
@ -201,7 +201,7 @@ mod array_functions {
list: &mut Array,
mapper: FnPtr,
) -> 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() {
array.push(
@ -233,7 +233,7 @@ mod array_functions {
list: &mut Array,
filter: FnPtr,
) -> 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() {
if filter
@ -367,7 +367,7 @@ mod array_functions {
list: &mut Array,
reducer: FnPtr,
) -> Result<Dynamic, Box<EvalAltResult>> {
let mut result: Dynamic = ().into();
let mut result: Dynamic = Dynamic::UNIT;
for (i, item) in list.iter().enumerate() {
result = reducer
@ -434,7 +434,7 @@ mod array_functions {
list: &mut Array,
reducer: FnPtr,
) -> Result<Dynamic, Box<EvalAltResult>> {
let mut result: Dynamic = ().into();
let mut result: Dynamic = Dynamic::UNIT;
for (i, item) in list.iter().enumerate().rev() {
result = reducer
@ -529,7 +529,7 @@ mod array_functions {
})
});
Ok(().into())
Ok(Dynamic::UNIT)
}
#[rhai_fn(return_raw)]
pub fn drain(
@ -537,7 +537,7 @@ mod array_functions {
list: &mut Array,
filter: FnPtr,
) -> 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();
@ -596,7 +596,7 @@ mod array_functions {
list: &mut Array,
filter: FnPtr,
) -> 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();

View File

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

View File

@ -56,22 +56,20 @@ pub(crate) struct PackagesCollection(StaticVec<PackageLibrary>);
impl 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.
self.0.insert(0, package);
}
/// Does the specified function hash key exist in the `PackagesCollection`?
#[allow(dead_code)]
pub fn contains_fn(&self, hash: u64, public_only: bool) -> bool {
self.0.iter().any(|p| p.contains_fn(hash, public_only))
pub fn contains_fn(&self, hash: u64) -> bool {
self.0.iter().any(|p| p.contains_fn(hash, false))
}
/// Get specified function via its hash key.
pub fn get_fn(&self, hash: u64, public_only: bool) -> Option<&CallableFunction> {
self.0
.iter()
.map(|p| p.get_fn(hash, public_only))
.find(|f| f.is_some())
.flatten()
pub fn get_fn(&self, hash: u64) -> Option<&CallableFunction> {
self.0.iter().find_map(|p| p.get_fn(hash, false))
}
/// Does the specified TypeId iterator exist in the `PackagesCollection`?
#[allow(dead_code)]
@ -80,11 +78,7 @@ impl PackagesCollection {
}
/// Get the specified TypeId iterator.
pub fn get_iter(&self, id: TypeId) -> Option<IteratorFn> {
self.0
.iter()
.map(|p| p.get_iter(id))
.find(|f| f.is_some())
.flatten()
self.0.iter().find_map(|p| p.get_iter(id))
}
}
@ -128,7 +122,7 @@ macro_rules! def_package {
impl $package {
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(module.into())
}

View File

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

View File

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

View File

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

View File

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

View File

@ -6,9 +6,6 @@ use crate::token::{Position, NO_POS};
use crate::utils::ImmutableString;
use crate::INT;
#[cfg(not(feature = "no_function"))]
use crate::engine::is_anonymous_fn;
use crate::stdlib::{
boxed::Box,
error::Error,
@ -159,7 +156,7 @@ impl fmt::Display for EvalAltResult {
Self::ErrorParsing(p, _) => write!(f, "Syntax error: {}", p)?,
#[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)?
}
Self::ErrorInFunctionCall(s, err, _) => {
@ -303,7 +300,6 @@ impl EvalAltResult {
Self::LoopBreak(_, _) | Self::Return(_, _) => unreachable!(),
}
}
/// Is this error a system exception?
pub fn is_system_exception(&self) -> bool {
match self {
@ -322,7 +318,6 @@ impl EvalAltResult {
_ => false,
}
}
/// Get the `Position` of this error.
pub fn position(&self) -> Position {
match self {
@ -356,7 +351,6 @@ impl EvalAltResult {
| Self::Return(_, pos) => *pos,
}
}
/// Override the `Position` of this error.
pub fn set_position(&mut self, new_position: Position) {
match self {
@ -390,7 +384,6 @@ impl EvalAltResult {
| Self::Return(_, pos) => *pos = new_position,
}
}
/// Consume the current `EvalAltResult` and return a new one with the specified `Position`
/// if the current position is `Position::None`.
#[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.
// 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> {
/// Current value of the entry.
values: Vec<Dynamic>,
@ -75,6 +75,16 @@ pub struct Scope<'a> {
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> {
/// Create a new Scope.
///
@ -417,6 +427,7 @@ impl<'a> Scope<'a> {
}
/// Get an iterator to entries in the Scope.
#[inline(always)]
#[allow(dead_code)]
pub(crate) fn into_iter(
self,
) -> impl Iterator<Item = (Cow<'a, str>, EntryType, Dynamic, Vec<String>)> {

View File

@ -13,13 +13,10 @@ use serde::de::{
use serde::Deserialize;
#[cfg(not(feature = "no_index"))]
use crate::engine::Array;
use crate::Array;
#[cfg(not(feature = "no_object"))]
use crate::engine::Map;
#[cfg(not(feature = "no_object"))]
use serde::de::{EnumAccess, VariantAccess};
use crate::Map;
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"))]
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 Variant = Self;
@ -556,7 +553,7 @@ impl<'t, 'de> EnumAccess<'de> for EnumDeserializer<'t, 'de> {
}
#[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>;
fn unit_variant(mut self) -> Result<(), Self::Error> {

View File

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

View File

@ -193,7 +193,6 @@ impl Engine {
Ok(self)
}
/// Register a custom syntax with the `Engine`.
///
/// ## 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,
};
#[cfg(not(feature = "no_closure"))]
use crate::engine::KEYWORD_IS_SHARED;
use crate::parse_error::LexError;
use crate::StaticVec;
use crate::INT;
@ -545,7 +542,7 @@ impl Token {
| KEYWORD_IS_DEF_FN | KEYWORD_THIS => Reserved(syntax.into()),
#[cfg(not(feature = "no_closure"))]
KEYWORD_IS_SHARED => Reserved(syntax.into()),
crate::engine::KEYWORD_IS_SHARED => Reserved(syntax.into()),
_ => return None,
})
@ -1515,7 +1512,7 @@ fn get_identifier(
pub fn is_keyword_function(name: &str) -> bool {
match name {
#[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_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_DEF_VAR | KEYWORD_IS_DEF_FN => {
true

View File

@ -1,14 +1,25 @@
use rhai::{Engine, INT};
use rhai::{Engine, EvalAltResult, INT};
#[test]
fn test_comments() {
fn test_comments() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
assert!(engine
.eval::<INT>("let x = 5; x // I am a single line comment, yay!")
.is_ok());
assert_eq!(
engine.eval::<INT>("let x = 42; x // I am a single line comment, yay!")?,
42
);
assert!(engine
.eval::<INT>("let /* I am a multi-line comment, yay! */ x = 5; x")
.is_ok());
assert_eq!(
engine.eval::<INT>(
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"))]
#[test]
@ -75,3 +75,45 @@ fn test_for_object() -> Result<(), Box<EvalAltResult>> {
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);
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);
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);
let mut resolver = StaticModuleResolver::new();
resolver.insert("question", module);
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!(
engine.eval::<INT>(r#"import "question" as q; q::MYSTIC_NUMBER"#)?,
engine.eval::<INT>("question::life::universe::answer + 1")?,
42
);
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
);
assert!(engine
.eval::<INT>("inc(question::life::universe::answer)")
.is_err());
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<INT>(
r#"import "question" as q; q::life::universe::inc(q::life::universe::answer)"#
)?,
engine.eval::<INT>("super_inc(question::life::universe::answer)")?,
42
);
#[cfg(feature = "no_object")]
assert!(engine
.eval::<INT>("super_inc(question::life::universe::answer)")
.is_err());
Ok(())
}

View File

@ -62,3 +62,59 @@ fn test_switch() -> Result<(), Box<EvalAltResult>> {
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]
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
assert_eq!(
*engine
.register_custom_syntax(&["!"], 0, |_, _| Ok(().into()))
.register_custom_syntax(&["!"], 0, |_, _| Ok(Dynamic::UNIT))
.expect_err("should error")
.0,
ParseErrorType::BadInput(LexError::ImproperSymbol(