Merge pull request #293 from schungx/master
Add Engine::register_module.
This commit is contained in:
commit
b21d5e6cee
@ -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
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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 |
|
||||
|
@ -216,7 +216,7 @@ fn implementation_func(
|
||||
}
|
||||
}
|
||||
|
||||
Ok(().into())
|
||||
Ok(Dynamic::UNIT)
|
||||
}
|
||||
|
||||
// Register the custom syntax (sample): exec |x| -> { x += 1 } while x < 0;
|
||||
|
@ -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.
|
||||
|
@ -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
229
doc/src/patterns/enums.md
Normal 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
|
||||
}
|
||||
```
|
@ -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())
|
||||
})
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
-------------------------------------
|
||||
|
||||
|
@ -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`]).
|
||||
|
@ -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>>'
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
||||
|
163
src/ast.rs
163
src/ast.rs
@ -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(_, _)
|
||||
|
@ -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()
|
||||
|
446
src/engine.rs
446
src/engine.rs
@ -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();
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
|
@ -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
|
||||
|
@ -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).
|
||||
|
@ -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()),
|
||||
|
@ -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).
|
||||
///
|
||||
|
@ -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};
|
||||
|
||||
|
@ -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,8 +1390,14 @@ impl Module {
|
||||
}
|
||||
});
|
||||
|
||||
// Modules left in the scope become sub-modules
|
||||
mods.iter().for_each(|(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);
|
||||
});
|
||||
|
||||
@ -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
|
||||
let hash_qualified_script =
|
||||
if cfg!(feature = "no_object") && qualifiers.is_empty() {
|
||||
hash
|
||||
} else {
|
||||
// Qualifiers + function name + number of arguments.
|
||||
calc_script_fn_hash(qualifiers.iter().map(|&v| v), &name, *params)
|
||||
crate::calc_script_fn_hash(
|
||||
qualifiers.iter().map(|&v| v),
|
||||
&name,
|
||||
*params,
|
||||
)
|
||||
};
|
||||
functions.push((hash_qualified_script, func.clone()));
|
||||
functions.insert(hash_qualified_script, func.clone());
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if !self.indexed {
|
||||
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()
|
||||
|
142
src/optimize.rs
142
src/optimize.rs
@ -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,
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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},
|
||||
|
@ -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 {
|
||||
|
@ -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 = "==")]
|
||||
|
112
src/parser.rs
112
src/parser.rs
@ -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,
|
||||
|
@ -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)]
|
||||
|
13
src/scope.rs
13
src/scope.rs
@ -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>)> {
|
||||
|
@ -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> {
|
||||
|
@ -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>;
|
||||
|
||||
|
@ -193,7 +193,6 @@ impl Engine {
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Register a custom syntax with the `Engine`.
|
||||
///
|
||||
/// ## WARNING - Low Level API
|
||||
|
@ -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
|
||||
|
@ -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(())
|
||||
}
|
||||
|
44
tests/for.rs
44
tests/for.rs
@ -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(())
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user