commit
4f4626cc25
@ -6,7 +6,7 @@ members = [
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "rhai"
|
name = "rhai"
|
||||||
version = "0.19.5"
|
version = "0.19.6"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
|
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
|
||||||
description = "Embedded scripting for Rust"
|
description = "Embedded scripting for Rust"
|
||||||
|
15
RELEASES.md
15
RELEASES.md
@ -1,6 +1,17 @@
|
|||||||
Rhai Release Notes
|
Rhai Release Notes
|
||||||
==================
|
==================
|
||||||
|
|
||||||
|
Version 0.19.6
|
||||||
|
==============
|
||||||
|
|
||||||
|
This version adds the `switch` statement.
|
||||||
|
|
||||||
|
New features
|
||||||
|
------------
|
||||||
|
|
||||||
|
* `switch` statement.
|
||||||
|
|
||||||
|
|
||||||
Version 0.19.5
|
Version 0.19.5
|
||||||
==============
|
==============
|
||||||
|
|
||||||
@ -209,7 +220,7 @@ Bug fixes
|
|||||||
---------
|
---------
|
||||||
|
|
||||||
* Fixes bug that prevents calling functions in closures.
|
* Fixes bug that prevents calling functions in closures.
|
||||||
* Fixes bug that erroneously consumes the first argument to a module-qualified function call.
|
* Fixes bug that erroneously consumes the first argument to a namespace-qualified function call.
|
||||||
|
|
||||||
New features
|
New features
|
||||||
------------
|
------------
|
||||||
@ -299,7 +310,7 @@ New features
|
|||||||
* The boolean `^` (XOR) operator is added.
|
* The boolean `^` (XOR) operator is added.
|
||||||
* `FnPtr` is exposed as the function pointer type.
|
* `FnPtr` is exposed as the function pointer type.
|
||||||
* `rhai::module_resolvers::ModuleResolversCollection` added to try a list of module resolvers.
|
* `rhai::module_resolvers::ModuleResolversCollection` added to try a list of module resolvers.
|
||||||
* It is now possible to mutate the first argument of a module-qualified function call when the argument is a simple variable (but not a module constant).
|
* It is now possible to mutate the first argument of a namespace-qualified function call when the argument is a simple variable (but not a module constant).
|
||||||
* Many configuration/setting API's now returns `&mut Self` so that the calls can be chained.
|
* Many configuration/setting API's now returns `&mut Self` so that the calls can be chained.
|
||||||
* `String` parameters in functions are supported (but inefficiently).
|
* `String` parameters in functions are supported (but inefficiently).
|
||||||
|
|
||||||
|
@ -77,13 +77,14 @@ The Rhai Scripting Language
|
|||||||
7. [Logic Operators](language/logic.md)
|
7. [Logic Operators](language/logic.md)
|
||||||
8. [Other Operators](language/other-op.md)
|
8. [Other Operators](language/other-op.md)
|
||||||
9. [If Statement](language/if.md)
|
9. [If Statement](language/if.md)
|
||||||
10. [While Loop](language/while.md)
|
10. [Switch Expression](language/switch.md)
|
||||||
11. [Loop Statement](language/loop.md)
|
11. [While Loop](language/while.md)
|
||||||
12. [For Loop](language/for.md)
|
12. [Loop Statement](language/loop.md)
|
||||||
13. [Return Values](language/return.md)
|
13. [For Loop](language/for.md)
|
||||||
14. [Throw Exception on Error](language/throw.md)
|
14. [Return Values](language/return.md)
|
||||||
15. [Catch Exceptions](language/try-catch.md)
|
15. [Throw Exception on Error](language/throw.md)
|
||||||
16. [Functions](language/functions.md)
|
16. [Catch Exceptions](language/try-catch.md)
|
||||||
|
17. [Functions](language/functions.md)
|
||||||
1. [Call Method as Function](language/method.md)
|
1. [Call Method as Function](language/method.md)
|
||||||
2. [Overloading](language/overload.md)
|
2. [Overloading](language/overload.md)
|
||||||
3. [Namespaces](language/fn-namespaces.md)
|
3. [Namespaces](language/fn-namespaces.md)
|
||||||
@ -91,11 +92,11 @@ The Rhai Scripting Language
|
|||||||
5. [Currying](language/fn-curry.md)
|
5. [Currying](language/fn-curry.md)
|
||||||
6. [Anonymous Functions](language/fn-anon.md)
|
6. [Anonymous Functions](language/fn-anon.md)
|
||||||
7. [Closures](language/fn-closure.md)
|
7. [Closures](language/fn-closure.md)
|
||||||
17. [Print and Debug](language/print-debug.md)
|
18. [Print and Debug](language/print-debug.md)
|
||||||
18. [Modules](language/modules/index.md)
|
19. [Modules](language/modules/index.md)
|
||||||
1. [Export Variables, Functions and Sub-Modules](language/modules/export.md)
|
1. [Export Variables, Functions and Sub-Modules](language/modules/export.md)
|
||||||
2. [Import Modules](language/modules/import.md)
|
2. [Import Modules](language/modules/import.md)
|
||||||
19. [Eval Statement](language/eval.md)
|
20. [Eval Statement](language/eval.md)
|
||||||
6. [Safety and Protection](safety/index.md)
|
6. [Safety and Protection](safety/index.md)
|
||||||
1. [Checked Arithmetic](safety/checked.md)
|
1. [Checked Arithmetic](safety/checked.md)
|
||||||
2. [Sand-Boxing](safety/sandbox.md)
|
2. [Sand-Boxing](safety/sandbox.md)
|
||||||
|
@ -13,6 +13,7 @@ Keywords List
|
|||||||
| `is_shared` | is a value shared? | [`no_closure`] | yes | no |
|
| `is_shared` | is a value shared? | [`no_closure`] | yes | no |
|
||||||
| `if` | if statement | | no | |
|
| `if` | if statement | | no | |
|
||||||
| `else` | else block of if statement | | no | |
|
| `else` | else block of if statement | | no | |
|
||||||
|
| `switch` | matching | | no | |
|
||||||
| `while` | while loop | | no | |
|
| `while` | while loop | | no | |
|
||||||
| `loop` | infinite loop | | no | |
|
| `loop` | infinite loop | | no | |
|
||||||
| `for` | for loop | | no | |
|
| `for` | for loop | | no | |
|
||||||
@ -52,7 +53,6 @@ Reserved Keywords
|
|||||||
| `then` | control flow |
|
| `then` | control flow |
|
||||||
| `goto` | control flow |
|
| `goto` | control flow |
|
||||||
| `exit` | control flow |
|
| `exit` | control flow |
|
||||||
| `switch` | matching |
|
|
||||||
| `match` | matching |
|
| `match` | matching |
|
||||||
| `case` | matching |
|
| `case` | matching |
|
||||||
| `public` | function/field access |
|
| `public` | function/field access |
|
||||||
|
@ -39,6 +39,7 @@ Symbols and Patterns
|
|||||||
|
|
||||||
| Symbol | Name | Description |
|
| Symbol | Name | Description |
|
||||||
| ---------------------------------- | :------------------: | ------------------------------------- |
|
| ---------------------------------- | :------------------: | ------------------------------------- |
|
||||||
|
| `_` | underscore | default `switch` case |
|
||||||
| `;` | semicolon | statement separator |
|
| `;` | semicolon | statement separator |
|
||||||
| `,` | comma | list separator |
|
| `,` | comma | list separator |
|
||||||
| `:` | colon | [object map] property value separator |
|
| `:` | colon | [object map] property value separator |
|
||||||
@ -52,6 +53,7 @@ Symbols and Patterns
|
|||||||
| <code>\|</code> .. <code>\|</code> | pipes | closure |
|
| <code>\|</code> .. <code>\|</code> | pipes | closure |
|
||||||
| `[` .. `]` | brackets | [array] literal |
|
| `[` .. `]` | brackets | [array] literal |
|
||||||
| `!` | bang | function call in calling scope |
|
| `!` | bang | function call in calling scope |
|
||||||
|
| `=>` | double arrow | `switch` expression case separator |
|
||||||
| `//` | comment | line comment |
|
| `//` | comment | line comment |
|
||||||
| `/*` .. `*/` | comment | block comment |
|
| `/*` .. `*/` | comment | block comment |
|
||||||
| `(*` .. `*)` | comment | _reserved_ |
|
| `(*` .. `*)` | comment | _reserved_ |
|
||||||
@ -64,7 +66,6 @@ Symbols and Patterns
|
|||||||
| `#` | hash | _reserved_ |
|
| `#` | hash | _reserved_ |
|
||||||
| `@` | at | _reserved_ |
|
| `@` | at | _reserved_ |
|
||||||
| `$` | dollar | _reserved_ |
|
| `$` | dollar | _reserved_ |
|
||||||
| `=>` | double arrow | _reserved_ |
|
|
||||||
| `->` | arrow | _reserved_ |
|
| `->` | arrow | _reserved_ |
|
||||||
| `<-` | left arrow | _reserved_ |
|
| `<-` | left arrow | _reserved_ |
|
||||||
| `===` | strict equals to | _reserved_ |
|
| `===` | strict equals to | _reserved_ |
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "0.19.5",
|
"version": "0.19.6",
|
||||||
"repoHome": "https://github.com/jonathandturner/rhai/blob/master",
|
"repoHome": "https://github.com/jonathandturner/rhai/blob/master",
|
||||||
"repoTree": "https://github.com/jonathandturner/rhai/tree/master",
|
"repoTree": "https://github.com/jonathandturner/rhai/tree/master",
|
||||||
"rootUrl": "",
|
"rootUrl": "",
|
||||||
|
@ -49,7 +49,7 @@ For example:
|
|||||||
let animal = "rabbit";
|
let animal = "rabbit";
|
||||||
let food = "carrot";
|
let food = "carrot";
|
||||||
|
|
||||||
animal eats food // custom operator - 'eats'
|
animal eats food // custom operator 'eats'
|
||||||
|
|
||||||
eats(animal, food) // <- the above really de-sugars to this
|
eats(animal, food) // <- the above really de-sugars to this
|
||||||
```
|
```
|
||||||
@ -61,7 +61,7 @@ nevertheless it makes the DSL syntax much simpler and expressive.
|
|||||||
Custom Syntax
|
Custom Syntax
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
For advanced DSL scenarios, it is possible to define entire expression [_syntax_][custom syntax] -
|
For advanced DSL scenarios, it is possible to define entire expression [_syntax_][custom syntax] &ndash
|
||||||
essentially custom statement types.
|
essentially custom statement types.
|
||||||
|
|
||||||
For example, the following is a SQL-like syntax for some obscure DSL operation:
|
For example, the following is a SQL-like syntax for some obscure DSL operation:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
`Scope` - Initializing and Maintaining State
|
`Scope` - Initializing and Maintaining State
|
||||||
===========================================
|
=================================================
|
||||||
|
|
||||||
{{#include ../links.md}}
|
{{#include ../links.md}}
|
||||||
|
|
||||||
|
@ -12,27 +12,19 @@ Use `type_of()` to Get Value Type
|
|||||||
Because [`type_of()`] a `Dynamic` value returns the type of the actual value,
|
Because [`type_of()`] a `Dynamic` value returns the type of the actual value,
|
||||||
it is usually used to perform type-specific actions based on the actual value's type.
|
it is usually used to perform type-specific actions based on the actual value's type.
|
||||||
|
|
||||||
```rust
|
```c
|
||||||
let mystery = get_some_dynamic_value();
|
let mystery = get_some_dynamic_value();
|
||||||
|
|
||||||
if type_of(mystery) == "i64" {
|
switch mystery {
|
||||||
print("Hey, I got an integer here!");
|
"i64" => print("Hey, I got an integer here!"),
|
||||||
} else if type_of(mystery) == "f64" {
|
"f64" => print("Hey, I got a float here!"),
|
||||||
print("Hey, I got a float here!");
|
"string" => print("Hey, I got a string here!"),
|
||||||
} else if type_of(mystery) == "string" {
|
"bool" => print("Hey, I got a boolean here!"),
|
||||||
print("Hey, I got a string here!");
|
"array" => print("Hey, I got an array here!"),
|
||||||
} else if type_of(mystery) == "bool" {
|
"map" => print("Hey, I got an object map here!"),
|
||||||
print("Hey, I got a boolean here!");
|
"Fn" => print("Hey, I got a function pointer here!"),
|
||||||
} else if type_of(mystery) == "array" {
|
"TestStruct" => print("Hey, I got the TestStruct custom type here!"),
|
||||||
print("Hey, I got an array here!");
|
_ => print("I don't know what this is: " + type_of(mystery))
|
||||||
} else if type_of(mystery) == "map" {
|
|
||||||
print("Hey, I got an object map here!");
|
|
||||||
} else if type_of(mystery) == "Fn" {
|
|
||||||
print("Hey, I got a function pointer here!");
|
|
||||||
} else if type_of(mystery) == "TestStruct" {
|
|
||||||
print("Hey, I got the TestStruct custom type here!");
|
|
||||||
} else {
|
|
||||||
print("I don't know what this is: " + type_of(mystery));
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -16,9 +16,9 @@ allow combining all functions in one [`AST`] into another, forming a new, unifie
|
|||||||
In general, there are two types of _namespaces_ where functions are looked up:
|
In general, there are two types of _namespaces_ where functions are looked up:
|
||||||
|
|
||||||
| Namespace | Source | Lookup method | Sub-modules? | Variables? |
|
| Namespace | Source | Lookup method | Sub-modules? | Variables? |
|
||||||
| --------- | ------------------------------------------------------------------------------------- | ------------------------------ | :----------: | :--------: |
|
| --------- | ------------------------------------------------------------------------------------- | --------------------------------- | :----------: | :--------: |
|
||||||
| Global | 1) `Engine::register_XXX` API<br/>2) [`AST`] being evaluated<br/>3) [packages] loaded | simple function name | ignored | ignored |
|
| Global | 1) `Engine::register_XXX` API<br/>2) [`AST`] being evaluated<br/>3) [packages] loaded | simple function name | ignored | ignored |
|
||||||
| Module | [`Module`] | module-qualified function name | yes | yes |
|
| Module | [`Module`] | namespace-qualified function name | yes | yes |
|
||||||
|
|
||||||
|
|
||||||
Global Namespace
|
Global Namespace
|
||||||
|
97
doc/src/language/switch.md
Normal file
97
doc/src/language/switch.md
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
`switch` Expression
|
||||||
|
===================
|
||||||
|
|
||||||
|
{{#include ../links.md}}
|
||||||
|
|
||||||
|
The `switch` _expression_ allows matching on literal values, and it mostly follows Rust's
|
||||||
|
`match` syntax:
|
||||||
|
|
||||||
|
```c
|
||||||
|
switch calc_secret_value(x) {
|
||||||
|
1 => print("It's one!"),
|
||||||
|
2 => {
|
||||||
|
print("It's two!");
|
||||||
|
print("Again!");
|
||||||
|
}
|
||||||
|
3 => print("Go!"),
|
||||||
|
// _ is the default when no cases match
|
||||||
|
_ => print("Oops! Something's wrong: " + x)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Expression, Not Statement
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
`switch` is not a statement, but an expression. This means that a `switch` expression can
|
||||||
|
appear anywhere a regular expression can, e.g. as function call arguments.
|
||||||
|
|
||||||
|
```c
|
||||||
|
let x = switch foo { 1 => true, _ => false };
|
||||||
|
|
||||||
|
func(switch foo {
|
||||||
|
"hello" => 42,
|
||||||
|
"world" => 123,
|
||||||
|
_ => 0
|
||||||
|
});
|
||||||
|
|
||||||
|
// The above is somewhat equivalent to:
|
||||||
|
|
||||||
|
let x = if foo == 1 { true } else { false };
|
||||||
|
|
||||||
|
if foo == "hello" {
|
||||||
|
func(42);
|
||||||
|
} else if foo == "world" {
|
||||||
|
func(123);
|
||||||
|
} else {
|
||||||
|
func(0);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Array and Object Map Literals Also Work
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
The `switch` expression can match against any _literal_, including [array] and [object map] literals.
|
||||||
|
|
||||||
|
```c
|
||||||
|
// Match on arrays
|
||||||
|
switch [foo, bar, baz] {
|
||||||
|
["hello", 42, true] => { ... }
|
||||||
|
["hello", 123, false] => { ... }
|
||||||
|
["world", 1, true] => { ... }
|
||||||
|
_ => { ... }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match on object maps
|
||||||
|
switch map {
|
||||||
|
#{ a: 1, b: 2, c: true } => { ... }
|
||||||
|
#{ a: 42, d: "hello" } => { ... }
|
||||||
|
_ => { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Difference From If-Else Chain
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
Although a `switch` expression looks _almost_ the same as an `if`-`else` chain,
|
||||||
|
there are subtle differences between the two.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
`switch` expressions do _not_ use the `==` operator for comparison;
|
||||||
|
instead, they _hash_ the data values and jump directly to the correct
|
||||||
|
statements via a pre-compiled look-up table. This makes matching extremely
|
||||||
|
efficient, but it also means that [overloading][operator overloading]
|
||||||
|
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.
|
@ -69,9 +69,9 @@ resolver.insert("question", module);
|
|||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
engine.set_module_resolver(Some(resolver));
|
engine.set_module_resolver(Some(resolver));
|
||||||
|
|
||||||
// Use module-qualified variables
|
// Use namespace-qualified variables
|
||||||
engine.eval::<i64>(r#"import "question" as q; q::answer + 1"#)? == 42;
|
engine.eval::<i64>(r#"import "question" as q; q::answer + 1"#)? == 42;
|
||||||
|
|
||||||
// Call module-qualified functions
|
// Call namespace-qualified functions
|
||||||
engine.eval::<i64>(r#"import "question" as q; q::inc(q::answer)"#)? == 42;
|
engine.eval::<i64>(r#"import "question" as q; q::inc(q::answer)"#)? == 42;
|
||||||
```
|
```
|
||||||
|
218
src/ast.rs
218
src/ast.rs
@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
use crate::dynamic::{Dynamic, Union};
|
use crate::dynamic::{Dynamic, Union};
|
||||||
use crate::fn_native::{FnPtr, Shared};
|
use crate::fn_native::{FnPtr, Shared};
|
||||||
use crate::module::{Module, ModuleRef};
|
use crate::module::{Module, NamespaceRef};
|
||||||
use crate::syntax::FnCustomSyntaxEval;
|
use crate::syntax::FnCustomSyntaxEval;
|
||||||
use crate::token::{Position, Token, NO_POS};
|
use crate::token::{Position, Token, NO_POS};
|
||||||
use crate::utils::ImmutableString;
|
use crate::utils::{ImmutableString, StraightHasherBuilder};
|
||||||
use crate::StaticVec;
|
use crate::StaticVec;
|
||||||
use crate::INT;
|
use crate::INT;
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ use crate::FLOAT;
|
|||||||
use crate::engine::Array;
|
use crate::engine::Array;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
use crate::engine::{make_getter, make_setter, Map};
|
use crate::engine::Map;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
use crate::engine::Imports;
|
use crate::engine::Imports;
|
||||||
@ -25,8 +25,9 @@ use crate::stdlib::{
|
|||||||
any::TypeId,
|
any::TypeId,
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
|
collections::HashMap,
|
||||||
fmt,
|
fmt,
|
||||||
hash::{Hash, Hasher},
|
hash::Hash,
|
||||||
num::NonZeroUsize,
|
num::NonZeroUsize,
|
||||||
ops::{Add, AddAssign},
|
ops::{Add, AddAssign},
|
||||||
string::String,
|
string::String,
|
||||||
@ -34,9 +35,6 @@ use crate::stdlib::{
|
|||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
|
||||||
use crate::stdlib::ops::Neg;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
use crate::stdlib::collections::HashSet;
|
use crate::stdlib::collections::HashSet;
|
||||||
|
|
||||||
@ -98,10 +96,10 @@ pub struct ScriptFnDef {
|
|||||||
/// Function access mode.
|
/// Function access mode.
|
||||||
pub access: FnAccess,
|
pub access: FnAccess,
|
||||||
/// Names of function parameters.
|
/// Names of function parameters.
|
||||||
pub params: StaticVec<String>,
|
pub params: StaticVec<ImmutableString>,
|
||||||
/// Access to external variables.
|
/// Access to external variables.
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
pub externals: HashSet<String>,
|
pub externals: HashSet<ImmutableString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ScriptFnDef {
|
impl fmt::Display for ScriptFnDef {
|
||||||
@ -602,7 +600,7 @@ pub enum ReturnType {
|
|||||||
/// ## WARNING
|
/// ## WARNING
|
||||||
///
|
///
|
||||||
/// This type is volatile and may change.
|
/// This type is volatile and may change.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Stmt {
|
pub enum Stmt {
|
||||||
/// No-op.
|
/// No-op.
|
||||||
Noop(Position),
|
Noop(Position),
|
||||||
@ -637,10 +635,10 @@ pub enum Stmt {
|
|||||||
Import(Expr, Option<Box<IdentX>>, Position),
|
Import(Expr, Option<Box<IdentX>>, Position),
|
||||||
/// export var as var, ...
|
/// export var as var, ...
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Export(Vec<(Ident, Option<Ident>)>, Position),
|
Export(Vec<(IdentX, Option<IdentX>)>, Position),
|
||||||
/// Convert a variable to shared.
|
/// Convert a variable to shared.
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
Share(Box<Ident>),
|
Share(IdentX),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Stmt {
|
impl Default for Stmt {
|
||||||
@ -778,7 +776,7 @@ impl Stmt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// _[INTERNALS]_ A type wrapping a custom syntax definition.
|
/// _[INTERNALS]_ A custom syntax definition.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
///
|
///
|
||||||
/// ## WARNING
|
/// ## WARNING
|
||||||
@ -799,13 +797,6 @@ impl fmt::Debug for CustomExpr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for CustomExpr {
|
|
||||||
#[inline(always)]
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
self.keywords.hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CustomExpr {
|
impl CustomExpr {
|
||||||
/// Get the keywords for this `CustomExpr`.
|
/// Get the keywords for this `CustomExpr`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -819,50 +810,13 @@ impl CustomExpr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// _[INTERNALS]_ A type wrapping a floating-point number.
|
/// _[INTERNALS]_ A binary expression.
|
||||||
/// Exported under the `internals` feature only.
|
|
||||||
///
|
|
||||||
/// This type is mainly used to provide a standard `Hash` implementation
|
|
||||||
/// to floating-point numbers, allowing `Expr` to derive `Hash` automatically.
|
|
||||||
///
|
|
||||||
/// ## WARNING
|
|
||||||
///
|
|
||||||
/// This type is volatile and may change.
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
|
||||||
#[derive(Debug, PartialEq, PartialOrd, Clone)]
|
|
||||||
pub struct FloatWrapper(pub FLOAT);
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
|
||||||
impl Hash for FloatWrapper {
|
|
||||||
#[inline(always)]
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
state.write(&self.0.to_le_bytes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
|
||||||
impl Neg for FloatWrapper {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn neg(self) -> Self::Output {
|
|
||||||
Self(-self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
|
||||||
impl From<INT> for FloatWrapper {
|
|
||||||
fn from(value: INT) -> Self {
|
|
||||||
Self(value as FLOAT)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A binary expression structure.
|
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
///
|
///
|
||||||
/// ## WARNING
|
/// ## WARNING
|
||||||
///
|
///
|
||||||
/// This type is volatile and may change.
|
/// This type is volatile and may change.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct BinaryExpr {
|
pub struct BinaryExpr {
|
||||||
/// LHS expression.
|
/// LHS expression.
|
||||||
pub lhs: Expr,
|
pub lhs: Expr,
|
||||||
@ -876,8 +830,8 @@ pub struct BinaryExpr {
|
|||||||
/// ## WARNING
|
/// ## WARNING
|
||||||
///
|
///
|
||||||
/// This type is volatile and may change.
|
/// This type is volatile and may change.
|
||||||
#[derive(Debug, Clone, Hash, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct FnCallInfo {
|
pub struct FnCallExpr {
|
||||||
/// Pre-calculated hash for a script-defined function of the same name and number of parameters.
|
/// Pre-calculated hash for a script-defined function of the same name and number of parameters.
|
||||||
pub hash: u64,
|
pub hash: u64,
|
||||||
/// Call native functions only? Set to `true` to skip searching for script-defined function overrides
|
/// Call native functions only? Set to `true` to skip searching for script-defined function overrides
|
||||||
@ -889,7 +843,7 @@ pub struct FnCallInfo {
|
|||||||
/// Type is `bool` in order for `FnCallInfo` to be `Hash`
|
/// Type is `bool` in order for `FnCallInfo` to be `Hash`
|
||||||
pub def_value: Option<bool>,
|
pub def_value: Option<bool>,
|
||||||
/// Namespace of the function, if any. Boxed because it occurs rarely.
|
/// Namespace of the function, if any. Boxed because it occurs rarely.
|
||||||
pub namespace: Option<Box<ModuleRef>>,
|
pub namespace: Option<Box<NamespaceRef>>,
|
||||||
/// Function name.
|
/// Function name.
|
||||||
/// Use `Cow<'static, str>` because a lot of operators (e.g. `==`, `>=`) are implemented as function calls
|
/// Use `Cow<'static, str>` because a lot of operators (e.g. `==`, `>=`) are implemented as function calls
|
||||||
/// and the function names are predictable, so no need to allocate a new `String`.
|
/// and the function names are predictable, so no need to allocate a new `String`.
|
||||||
@ -904,49 +858,58 @@ pub struct FnCallInfo {
|
|||||||
/// ## WARNING
|
/// ## WARNING
|
||||||
///
|
///
|
||||||
/// This type is volatile and may change.
|
/// This type is volatile and may change.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Expr {
|
pub enum Expr {
|
||||||
/// Integer constant.
|
/// Integer constant.
|
||||||
IntegerConstant(INT, Position),
|
IntegerConstant(INT, Position),
|
||||||
/// Floating-point constant.
|
/// Floating-point constant.
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
FloatConstant(FloatWrapper, Position),
|
FloatConstant(FLOAT, Position),
|
||||||
/// Character constant.
|
/// Character constant.
|
||||||
CharConstant(char, Position),
|
CharConstant(char, Position),
|
||||||
/// String constant.
|
/// String constant.
|
||||||
StringConstant(Box<IdentX>),
|
StringConstant(ImmutableString, Position),
|
||||||
/// FnPtr constant.
|
/// FnPtr constant.
|
||||||
FnPointer(Box<IdentX>),
|
FnPointer(ImmutableString, Position),
|
||||||
/// Variable access - (optional index, optional modules, hash, variable name)
|
|
||||||
Variable(Box<(Option<NonZeroUsize>, Option<Box<ModuleRef>>, u64, Ident)>),
|
|
||||||
/// Property access - (getter, setter), prop
|
|
||||||
Property(Box<((String, String), IdentX)>),
|
|
||||||
/// { stmt }
|
|
||||||
Stmt(Box<StaticVec<Stmt>>, Position),
|
|
||||||
/// Wrapped expression - should not be optimized away.
|
|
||||||
Expr(Box<Expr>),
|
|
||||||
/// func(expr, ... )
|
|
||||||
FnCall(Box<FnCallInfo>, Position),
|
|
||||||
/// lhs.rhs
|
|
||||||
Dot(Box<BinaryExpr>, Position),
|
|
||||||
/// expr[expr]
|
|
||||||
Index(Box<BinaryExpr>, Position),
|
|
||||||
/// [ expr, ... ]
|
/// [ expr, ... ]
|
||||||
Array(Box<StaticVec<Expr>>, Position),
|
Array(Box<StaticVec<Expr>>, Position),
|
||||||
/// #{ name:expr, ... }
|
/// #{ name:expr, ... }
|
||||||
Map(Box<StaticVec<(IdentX, Expr)>>, Position),
|
Map(Box<StaticVec<(IdentX, Expr)>>, Position),
|
||||||
/// lhs in rhs
|
|
||||||
In(Box<BinaryExpr>, Position),
|
|
||||||
/// lhs && rhs
|
|
||||||
And(Box<BinaryExpr>, Position),
|
|
||||||
/// lhs || rhs
|
|
||||||
Or(Box<BinaryExpr>, Position),
|
|
||||||
/// true
|
/// true
|
||||||
True(Position),
|
True(Position),
|
||||||
/// false
|
/// false
|
||||||
False(Position),
|
False(Position),
|
||||||
/// ()
|
/// ()
|
||||||
Unit(Position),
|
Unit(Position),
|
||||||
|
/// Variable access - (optional index, optional modules, hash, variable name)
|
||||||
|
Variable(Box<(Option<NonZeroUsize>, Option<Box<NamespaceRef>>, u64, IdentX)>),
|
||||||
|
/// Property access - (getter, setter), prop
|
||||||
|
Property(Box<((ImmutableString, ImmutableString), IdentX)>),
|
||||||
|
/// { stmt }
|
||||||
|
Stmt(Box<StaticVec<Stmt>>, Position),
|
||||||
|
/// Wrapped expression - should not be optimized away.
|
||||||
|
Expr(Box<Expr>),
|
||||||
|
/// func(expr, ... )
|
||||||
|
FnCall(Box<FnCallExpr>, Position),
|
||||||
|
/// lhs.rhs
|
||||||
|
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
|
||||||
|
And(Box<BinaryExpr>, Position),
|
||||||
|
/// lhs || rhs
|
||||||
|
Or(Box<BinaryExpr>, Position),
|
||||||
/// Custom syntax
|
/// Custom syntax
|
||||||
Custom(Box<CustomExpr>, Position),
|
Custom(Box<CustomExpr>, Position),
|
||||||
}
|
}
|
||||||
@ -970,8 +933,8 @@ impl Expr {
|
|||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Self::FloatConstant(_, _) => TypeId::of::<FLOAT>(),
|
Self::FloatConstant(_, _) => TypeId::of::<FLOAT>(),
|
||||||
Self::CharConstant(_, _) => TypeId::of::<char>(),
|
Self::CharConstant(_, _) => TypeId::of::<char>(),
|
||||||
Self::StringConstant(_) => TypeId::of::<ImmutableString>(),
|
Self::StringConstant(_, _) => TypeId::of::<ImmutableString>(),
|
||||||
Self::FnPointer(_) => TypeId::of::<FnPtr>(),
|
Self::FnPointer(_, _) => TypeId::of::<FnPtr>(),
|
||||||
Self::True(_) | Self::False(_) | Self::In(_, _) | Self::And(_, _) | Self::Or(_, _) => {
|
Self::True(_) | Self::False(_) | Self::In(_, _) | Self::And(_, _) | Self::Or(_, _) => {
|
||||||
TypeId::of::<bool>()
|
TypeId::of::<bool>()
|
||||||
}
|
}
|
||||||
@ -996,11 +959,11 @@ impl Expr {
|
|||||||
|
|
||||||
Self::IntegerConstant(x, _) => (*x).into(),
|
Self::IntegerConstant(x, _) => (*x).into(),
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Self::FloatConstant(x, _) => x.0.into(),
|
Self::FloatConstant(x, _) => (*x).into(),
|
||||||
Self::CharConstant(x, _) => (*x).into(),
|
Self::CharConstant(x, _) => (*x).into(),
|
||||||
Self::StringConstant(x) => x.name.clone().into(),
|
Self::StringConstant(x, _) => x.clone().into(),
|
||||||
Self::FnPointer(x) => Dynamic(Union::FnPtr(Box::new(FnPtr::new_unchecked(
|
Self::FnPointer(x, _) => Dynamic(Union::FnPtr(Box::new(FnPtr::new_unchecked(
|
||||||
x.name.clone(),
|
x.clone(),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
)))),
|
)))),
|
||||||
Self::True(_) => true.into(),
|
Self::True(_) => true.into(),
|
||||||
@ -1043,14 +1006,15 @@ impl Expr {
|
|||||||
|
|
||||||
Self::IntegerConstant(_, pos) => *pos,
|
Self::IntegerConstant(_, pos) => *pos,
|
||||||
Self::CharConstant(_, pos) => *pos,
|
Self::CharConstant(_, pos) => *pos,
|
||||||
Self::StringConstant(x) => x.pos,
|
Self::StringConstant(_, pos) => *pos,
|
||||||
Self::FnPointer(x) => x.pos,
|
Self::FnPointer(_, pos) => *pos,
|
||||||
Self::Array(_, pos) => *pos,
|
Self::Array(_, pos) => *pos,
|
||||||
Self::Map(_, pos) => *pos,
|
Self::Map(_, pos) => *pos,
|
||||||
Self::Property(x) => (x.1).pos,
|
Self::Property(x) => (x.1).pos,
|
||||||
Self::Stmt(_, pos) => *pos,
|
Self::Stmt(_, pos) => *pos,
|
||||||
Self::Variable(x) => (x.3).pos,
|
Self::Variable(x) => (x.3).pos,
|
||||||
Self::FnCall(_, pos) => *pos,
|
Self::FnCall(_, pos) => *pos,
|
||||||
|
Self::Switch(_, pos) => *pos,
|
||||||
|
|
||||||
Self::And(x, _) | Self::Or(x, _) | Self::In(x, _) => x.lhs.position(),
|
Self::And(x, _) | Self::Or(x, _) | Self::In(x, _) => x.lhs.position(),
|
||||||
|
|
||||||
@ -1074,14 +1038,15 @@ impl Expr {
|
|||||||
|
|
||||||
Self::IntegerConstant(_, pos) => *pos = new_pos,
|
Self::IntegerConstant(_, pos) => *pos = new_pos,
|
||||||
Self::CharConstant(_, pos) => *pos = new_pos,
|
Self::CharConstant(_, pos) => *pos = new_pos,
|
||||||
Self::StringConstant(x) => x.pos = new_pos,
|
Self::StringConstant(_, pos) => *pos = new_pos,
|
||||||
Self::FnPointer(x) => x.pos = new_pos,
|
Self::FnPointer(_, pos) => *pos = new_pos,
|
||||||
Self::Array(_, pos) => *pos = new_pos,
|
Self::Array(_, pos) => *pos = new_pos,
|
||||||
Self::Map(_, pos) => *pos = new_pos,
|
Self::Map(_, pos) => *pos = new_pos,
|
||||||
Self::Variable(x) => (x.3).pos = new_pos,
|
Self::Variable(x) => (x.3).pos = new_pos,
|
||||||
Self::Property(x) => (x.1).pos = new_pos,
|
Self::Property(x) => (x.1).pos = new_pos,
|
||||||
Self::Stmt(_, pos) => *pos = new_pos,
|
Self::Stmt(_, pos) => *pos = new_pos,
|
||||||
Self::FnCall(_, pos) => *pos = new_pos,
|
Self::FnCall(_, pos) => *pos = new_pos,
|
||||||
|
Self::Switch(_, pos) => *pos = new_pos,
|
||||||
Self::And(_, pos) | Self::Or(_, pos) | Self::In(_, pos) => *pos = new_pos,
|
Self::And(_, pos) | Self::Or(_, pos) | Self::In(_, pos) => *pos = new_pos,
|
||||||
Self::True(pos) | Self::False(pos) | Self::Unit(pos) => *pos = new_pos,
|
Self::True(pos) | Self::False(pos) | Self::Unit(pos) => *pos = new_pos,
|
||||||
Self::Dot(_, pos) | Self::Index(_, pos) => *pos = new_pos,
|
Self::Dot(_, pos) | Self::Index(_, pos) => *pos = new_pos,
|
||||||
@ -1123,39 +1088,6 @@ impl Expr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is the expression a simple constant literal?
|
|
||||||
pub fn is_literal(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Self::Expr(x) => x.is_literal(),
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
|
||||||
Self::FloatConstant(_, _) => true,
|
|
||||||
|
|
||||||
Self::IntegerConstant(_, _)
|
|
||||||
| Self::CharConstant(_, _)
|
|
||||||
| Self::StringConstant(_)
|
|
||||||
| Self::FnPointer(_)
|
|
||||||
| Self::True(_)
|
|
||||||
| Self::False(_)
|
|
||||||
| Self::Unit(_) => true,
|
|
||||||
|
|
||||||
// An array literal is literal if all items are literals
|
|
||||||
Self::Array(x, _) => x.iter().all(Self::is_literal),
|
|
||||||
|
|
||||||
// An map literal is literal if all items are literals
|
|
||||||
Self::Map(x, _) => x.iter().map(|(_, expr)| expr).all(Self::is_literal),
|
|
||||||
|
|
||||||
// Check in expression
|
|
||||||
Self::In(x, _) => match (&x.lhs, &x.rhs) {
|
|
||||||
(Self::StringConstant(_), Self::StringConstant(_))
|
|
||||||
| (Self::CharConstant(_, _), Self::StringConstant(_)) => true,
|
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is the expression a constant?
|
/// Is the expression a constant?
|
||||||
pub fn is_constant(&self) -> bool {
|
pub fn is_constant(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
@ -1166,8 +1098,8 @@ impl Expr {
|
|||||||
|
|
||||||
Self::IntegerConstant(_, _)
|
Self::IntegerConstant(_, _)
|
||||||
| Self::CharConstant(_, _)
|
| Self::CharConstant(_, _)
|
||||||
| Self::StringConstant(_)
|
| Self::StringConstant(_, _)
|
||||||
| Self::FnPointer(_)
|
| Self::FnPointer(_, _)
|
||||||
| Self::True(_)
|
| Self::True(_)
|
||||||
| Self::False(_)
|
| Self::False(_)
|
||||||
| Self::Unit(_) => true,
|
| Self::Unit(_) => true,
|
||||||
@ -1180,8 +1112,8 @@ impl Expr {
|
|||||||
|
|
||||||
// Check in expression
|
// Check in expression
|
||||||
Self::In(x, _) => match (&x.lhs, &x.rhs) {
|
Self::In(x, _) => match (&x.lhs, &x.rhs) {
|
||||||
(Self::StringConstant(_), Self::StringConstant(_))
|
(Self::StringConstant(_, _), Self::StringConstant(_, _))
|
||||||
| (Self::CharConstant(_, _), Self::StringConstant(_)) => true,
|
| (Self::CharConstant(_, _), Self::StringConstant(_, _)) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1199,7 +1131,7 @@ impl Expr {
|
|||||||
|
|
||||||
Self::IntegerConstant(_, _)
|
Self::IntegerConstant(_, _)
|
||||||
| Self::CharConstant(_, _)
|
| Self::CharConstant(_, _)
|
||||||
| Self::FnPointer(_)
|
| Self::FnPointer(_, _)
|
||||||
| Self::In(_, _)
|
| Self::In(_, _)
|
||||||
| Self::And(_, _)
|
| Self::And(_, _)
|
||||||
| Self::Or(_, _)
|
| Self::Or(_, _)
|
||||||
@ -1207,9 +1139,10 @@ impl Expr {
|
|||||||
| Self::False(_)
|
| Self::False(_)
|
||||||
| Self::Unit(_) => false,
|
| Self::Unit(_) => false,
|
||||||
|
|
||||||
Self::StringConstant(_)
|
Self::StringConstant(_, _)
|
||||||
| Self::Stmt(_, _)
|
| Self::Stmt(_, _)
|
||||||
| Self::FnCall(_, _)
|
| Self::FnCall(_, _)
|
||||||
|
| Self::Switch(_, _)
|
||||||
| Self::Dot(_, _)
|
| Self::Dot(_, _)
|
||||||
| Self::Index(_, _)
|
| Self::Index(_, _)
|
||||||
| Self::Array(_, _)
|
| Self::Array(_, _)
|
||||||
@ -1238,21 +1171,6 @@ impl Expr {
|
|||||||
Self::Custom(_, _) => false,
|
Self::Custom(_, _) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a `Variable` into a `Property`. All other variants are untouched.
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn into_property(self) -> Self {
|
|
||||||
match self {
|
|
||||||
Self::Variable(x) if x.1.is_none() => {
|
|
||||||
let ident = x.3;
|
|
||||||
let getter = make_getter(&ident.name);
|
|
||||||
let setter = make_setter(&ident.name);
|
|
||||||
Self::Property(Box::new(((getter, setter), ident.into())))
|
|
||||||
}
|
|
||||||
_ => self,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
use crate::fn_native::{FnPtr, SendSync};
|
use crate::fn_native::{FnPtr, SendSync};
|
||||||
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
|
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
|
||||||
use crate::utils::ImmutableString;
|
use crate::utils::ImmutableString;
|
||||||
use crate::INT;
|
use crate::{StaticVec, INT};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
use crate::fn_native::{shared_try_take, Locked, Shared};
|
use crate::fn_native::{shared_try_take, Locked, Shared};
|
||||||
@ -21,6 +21,7 @@ use crate::stdlib::{
|
|||||||
any::{type_name, Any, TypeId},
|
any::{type_name, Any, TypeId},
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
fmt,
|
fmt,
|
||||||
|
hash::{Hash, Hasher},
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
};
|
};
|
||||||
@ -361,6 +362,44 @@ impl Dynamic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Hash for Dynamic {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
match &self.0 {
|
||||||
|
Union::Unit(_) => ().hash(state),
|
||||||
|
Union::Bool(value) => value.hash(state),
|
||||||
|
Union::Str(s) => s.hash(state),
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
Union::Array(a) => a.hash(state),
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
Union::Map(m) => {
|
||||||
|
let mut buf: StaticVec<_> = m.keys().collect();
|
||||||
|
buf.sort();
|
||||||
|
|
||||||
|
buf.into_iter().for_each(|key| {
|
||||||
|
key.hash(state);
|
||||||
|
m[key].hash(state);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_closure"))]
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
|
Union::Shared(cell) => (*cell.borrow()).hash(state),
|
||||||
|
#[cfg(not(feature = "no_closure"))]
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
Union::Shared(cell) => (*cell.read().unwrap()).hash(state),
|
||||||
|
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Map the name of a standard type into a friendly form.
|
/// Map the name of a standard type into a friendly form.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn map_std_type_name(name: &str) -> &str {
|
pub(crate) fn map_std_type_name(name: &str) -> &str {
|
||||||
|
104
src/engine.rs
104
src/engine.rs
@ -1,10 +1,10 @@
|
|||||||
//! Main module defining the script evaluation `Engine`.
|
//! Main module defining the script evaluation `Engine`.
|
||||||
|
|
||||||
use crate::ast::{BinaryExpr, Expr, FnCallInfo, Ident, IdentX, ReturnType, Stmt};
|
use crate::ast::{BinaryExpr, Expr, FnCallExpr, Ident, IdentX, ReturnType, Stmt};
|
||||||
use crate::dynamic::{map_std_type_name, Dynamic, Union, Variant};
|
use crate::dynamic::{map_std_type_name, Dynamic, Union, Variant};
|
||||||
use crate::fn_call::run_builtin_op_assignment;
|
use crate::fn_call::run_builtin_op_assignment;
|
||||||
use crate::fn_native::{Callback, FnPtr, OnVarCallback, Shared};
|
use crate::fn_native::{Callback, FnPtr, OnVarCallback, Shared};
|
||||||
use crate::module::{Module, ModuleRef};
|
use crate::module::{Module, NamespaceRef};
|
||||||
use crate::optimize::OptimizationLevel;
|
use crate::optimize::OptimizationLevel;
|
||||||
use crate::packages::{Package, PackagesCollection, StandardPackage};
|
use crate::packages::{Package, PackagesCollection, StandardPackage};
|
||||||
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
|
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
|
||||||
@ -12,6 +12,7 @@ use crate::result::EvalAltResult;
|
|||||||
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
||||||
use crate::syntax::CustomSyntax;
|
use crate::syntax::CustomSyntax;
|
||||||
use crate::token::{Position, NO_POS};
|
use crate::token::{Position, NO_POS};
|
||||||
|
use crate::utils::get_hasher;
|
||||||
use crate::{calc_native_fn_hash, StaticVec};
|
use crate::{calc_native_fn_hash, StaticVec};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
@ -38,6 +39,7 @@ use crate::stdlib::{
|
|||||||
boxed::Box,
|
boxed::Box,
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
fmt, format,
|
fmt, format,
|
||||||
|
hash::{Hash, Hasher},
|
||||||
iter::{empty, once},
|
iter::{empty, once},
|
||||||
num::NonZeroUsize,
|
num::NonZeroUsize,
|
||||||
ops::DerefMut,
|
ops::DerefMut,
|
||||||
@ -593,7 +595,7 @@ pub struct Engine {
|
|||||||
|
|
||||||
/// Max limits.
|
/// Max limits.
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
pub(crate) limits_set: Limits,
|
pub(crate) limits: Limits,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Engine {
|
impl fmt::Debug for Engine {
|
||||||
@ -636,6 +638,7 @@ pub fn is_anonymous_fn(fn_name: &str) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Print/debug to stdout
|
/// Print/debug to stdout
|
||||||
|
#[inline(always)]
|
||||||
fn default_print(_s: &str) {
|
fn default_print(_s: &str) {
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
@ -647,15 +650,15 @@ fn default_print(_s: &str) {
|
|||||||
pub fn search_imports(
|
pub fn search_imports(
|
||||||
mods: &Imports,
|
mods: &Imports,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
modules: &ModuleRef,
|
namespace: &NamespaceRef,
|
||||||
) -> Result<Shared<Module>, Box<EvalAltResult>> {
|
) -> Result<Shared<Module>, Box<EvalAltResult>> {
|
||||||
let Ident { name: root, pos } = &modules[0];
|
let IdentX { name: root, pos } = &namespace[0];
|
||||||
|
|
||||||
// Qualified - check if the root module is directly indexed
|
// Qualified - check if the root module is directly indexed
|
||||||
let index = if state.always_search {
|
let index = if state.always_search {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
modules.index().map_or(0, NonZeroUsize::get)
|
namespace.index().map_or(0, NonZeroUsize::get)
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(if index > 0 {
|
Ok(if index > 0 {
|
||||||
@ -670,7 +673,7 @@ pub fn search_imports(
|
|||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
/// Create a new `Engine`
|
/// Create a new `Engine`
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
// Create the new scripting Engine
|
// Create the new scripting Engine
|
||||||
let mut engine = Self {
|
let mut engine = Self {
|
||||||
@ -710,7 +713,7 @@ impl Engine {
|
|||||||
},
|
},
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
limits_set: Limits {
|
limits: Limits {
|
||||||
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
|
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
|
||||||
max_expr_depth: MAX_EXPR_DEPTH,
|
max_expr_depth: MAX_EXPR_DEPTH,
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
@ -733,7 +736,7 @@ impl Engine {
|
|||||||
|
|
||||||
/// Create a new `Engine` with minimal built-in functions.
|
/// Create a new `Engine` with minimal built-in functions.
|
||||||
/// Use the `load_package` method to load additional packages of functions.
|
/// Use the `load_package` method to load additional packages of functions.
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
pub fn new_raw() -> Self {
|
pub fn new_raw() -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: Default::default(),
|
id: Default::default(),
|
||||||
@ -762,7 +765,7 @@ impl Engine {
|
|||||||
},
|
},
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
limits_set: Limits {
|
limits: Limits {
|
||||||
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
|
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
|
||||||
max_expr_depth: MAX_EXPR_DEPTH,
|
max_expr_depth: MAX_EXPR_DEPTH,
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
@ -780,7 +783,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Search for a variable within the scope or within imports,
|
/// Search for a variable within the scope or within imports,
|
||||||
/// depending on whether the variable name is qualified.
|
/// depending on whether the variable name is namespace-qualified.
|
||||||
pub(crate) fn search_namespace<'s, 'a>(
|
pub(crate) fn search_namespace<'s, 'a>(
|
||||||
&self,
|
&self,
|
||||||
scope: &'s mut Scope,
|
scope: &'s mut Scope,
|
||||||
@ -793,7 +796,7 @@ impl Engine {
|
|||||||
match expr {
|
match expr {
|
||||||
Expr::Variable(v) => match v.as_ref() {
|
Expr::Variable(v) => match v.as_ref() {
|
||||||
// Qualified variable
|
// Qualified variable
|
||||||
(_, Some(modules), hash_var, Ident { name, pos }) => {
|
(_, Some(modules), hash_var, IdentX { name, pos }) => {
|
||||||
let module = search_imports(mods, state, modules)?;
|
let module = search_imports(mods, state, modules)?;
|
||||||
let target = module.get_qualified_var(*hash_var).map_err(|mut err| {
|
let target = module.get_qualified_var(*hash_var).map_err(|mut err| {
|
||||||
match *err {
|
match *err {
|
||||||
@ -825,13 +828,13 @@ impl Engine {
|
|||||||
this_ptr: &'s mut Option<&mut Dynamic>,
|
this_ptr: &'s mut Option<&mut Dynamic>,
|
||||||
expr: &'a Expr,
|
expr: &'a Expr,
|
||||||
) -> Result<(Target<'s>, &'a str, ScopeEntryType, Position), Box<EvalAltResult>> {
|
) -> Result<(Target<'s>, &'a str, ScopeEntryType, Position), Box<EvalAltResult>> {
|
||||||
let (index, _, _, Ident { name, pos }) = match expr {
|
let (index, _, _, IdentX { name, pos }) = match expr {
|
||||||
Expr::Variable(v) => v.as_ref(),
|
Expr::Variable(v) => v.as_ref(),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if the variable is `this`
|
// Check if the variable is `this`
|
||||||
if name == KEYWORD_THIS {
|
if name.as_str() == KEYWORD_THIS {
|
||||||
if let Some(val) = this_ptr {
|
if let Some(val) = this_ptr {
|
||||||
return Ok(((*val).into(), KEYWORD_THIS, ScopeEntryType::Normal, *pos));
|
return Ok(((*val).into(), KEYWORD_THIS, ScopeEntryType::Normal, *pos));
|
||||||
} else {
|
} else {
|
||||||
@ -870,7 +873,7 @@ impl Engine {
|
|||||||
// Find the variable in the scope
|
// Find the variable in the scope
|
||||||
scope
|
scope
|
||||||
.get_index(name)
|
.get_index(name)
|
||||||
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(name.into(), *pos))?
|
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(name.to_string(), *pos))?
|
||||||
.0
|
.0
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1000,7 +1003,7 @@ impl Engine {
|
|||||||
match rhs {
|
match rhs {
|
||||||
// xxx.fn_name(arg_expr_list)
|
// xxx.fn_name(arg_expr_list)
|
||||||
Expr::FnCall(x, pos) if x.namespace.is_none() => {
|
Expr::FnCall(x, pos) if x.namespace.is_none() => {
|
||||||
let FnCallInfo {
|
let FnCallExpr {
|
||||||
name,
|
name,
|
||||||
native_only: native,
|
native_only: native,
|
||||||
hash,
|
hash,
|
||||||
@ -1073,7 +1076,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
// {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr
|
// {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr
|
||||||
Expr::FnCall(x, pos) if x.namespace.is_none() => {
|
Expr::FnCall(x, pos) if x.namespace.is_none() => {
|
||||||
let FnCallInfo {
|
let FnCallExpr {
|
||||||
name,
|
name,
|
||||||
native_only: native,
|
native_only: native,
|
||||||
hash,
|
hash,
|
||||||
@ -1157,7 +1160,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
// xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr
|
// xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr
|
||||||
Expr::FnCall(f, pos) if f.namespace.is_none() => {
|
Expr::FnCall(f, pos) if f.namespace.is_none() => {
|
||||||
let FnCallInfo {
|
let FnCallExpr {
|
||||||
name,
|
name,
|
||||||
native_only: native,
|
native_only: native,
|
||||||
hash,
|
hash,
|
||||||
@ -1209,14 +1212,7 @@ impl Engine {
|
|||||||
level: usize,
|
level: usize,
|
||||||
new_val: Option<(Dynamic, Position)>,
|
new_val: Option<(Dynamic, Position)>,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
let (
|
let (BinaryExpr { lhs, rhs }, chain_type, op_pos) = match expr {
|
||||||
BinaryExpr {
|
|
||||||
lhs: dot_lhs,
|
|
||||||
rhs: dot_rhs,
|
|
||||||
},
|
|
||||||
chain_type,
|
|
||||||
op_pos,
|
|
||||||
) = match expr {
|
|
||||||
Expr::Index(x, pos) => (x.as_ref(), ChainType::Index, *pos),
|
Expr::Index(x, pos) => (x.as_ref(), ChainType::Index, *pos),
|
||||||
Expr::Dot(x, pos) => (x.as_ref(), ChainType::Dot, *pos),
|
Expr::Dot(x, pos) => (x.as_ref(), ChainType::Dot, *pos),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
@ -1230,17 +1226,17 @@ impl Engine {
|
|||||||
state,
|
state,
|
||||||
lib,
|
lib,
|
||||||
this_ptr,
|
this_ptr,
|
||||||
dot_rhs,
|
rhs,
|
||||||
chain_type,
|
chain_type,
|
||||||
&mut idx_values,
|
&mut idx_values,
|
||||||
0,
|
0,
|
||||||
level,
|
level,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
match dot_lhs {
|
match lhs {
|
||||||
// id.??? or id[???]
|
// id.??? or id[???]
|
||||||
Expr::Variable(x) => {
|
Expr::Variable(x) => {
|
||||||
let Ident {
|
let IdentX {
|
||||||
name: var_name,
|
name: var_name,
|
||||||
pos: var_pos,
|
pos: var_pos,
|
||||||
} = &x.3;
|
} = &x.3;
|
||||||
@ -1249,7 +1245,7 @@ impl Engine {
|
|||||||
.map_err(|err| err.fill_position(*var_pos))?;
|
.map_err(|err| err.fill_position(*var_pos))?;
|
||||||
|
|
||||||
let (target, _, typ, pos) =
|
let (target, _, typ, pos) =
|
||||||
self.search_namespace(scope, mods, state, lib, this_ptr, dot_lhs)?;
|
self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?;
|
||||||
|
|
||||||
// Constants cannot be modified
|
// Constants cannot be modified
|
||||||
match typ {
|
match typ {
|
||||||
@ -1262,7 +1258,7 @@ impl Engine {
|
|||||||
|
|
||||||
let obj_ptr = &mut target.into();
|
let obj_ptr = &mut target.into();
|
||||||
self.eval_dot_index_chain_helper(
|
self.eval_dot_index_chain_helper(
|
||||||
mods, state, lib, &mut None, obj_ptr, dot_rhs, idx_values, chain_type, level,
|
mods, state, lib, &mut None, obj_ptr, rhs, idx_values, chain_type, level,
|
||||||
new_val,
|
new_val,
|
||||||
)
|
)
|
||||||
.map(|(v, _)| v)
|
.map(|(v, _)| v)
|
||||||
@ -1275,7 +1271,7 @@ impl Engine {
|
|||||||
let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||||
let obj_ptr = &mut val.into();
|
let obj_ptr = &mut val.into();
|
||||||
self.eval_dot_index_chain_helper(
|
self.eval_dot_index_chain_helper(
|
||||||
mods, state, lib, this_ptr, obj_ptr, dot_rhs, idx_values, chain_type, level,
|
mods, state, lib, this_ptr, obj_ptr, rhs, idx_values, chain_type, level,
|
||||||
new_val,
|
new_val,
|
||||||
)
|
)
|
||||||
.map(|(v, _)| v)
|
.map(|(v, _)| v)
|
||||||
@ -1549,12 +1545,10 @@ impl Engine {
|
|||||||
|
|
||||||
Expr::IntegerConstant(x, _) => Ok((*x).into()),
|
Expr::IntegerConstant(x, _) => Ok((*x).into()),
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Expr::FloatConstant(x, _) => Ok(x.0.into()),
|
Expr::FloatConstant(x, _) => Ok((*x).into()),
|
||||||
Expr::StringConstant(x) => Ok(x.name.clone().into()),
|
Expr::StringConstant(x, _) => Ok(x.clone().into()),
|
||||||
Expr::CharConstant(x, _) => Ok((*x).into()),
|
Expr::CharConstant(x, _) => Ok((*x).into()),
|
||||||
Expr::FnPointer(x) => {
|
Expr::FnPointer(x, _) => Ok(FnPtr::new_unchecked(x.clone(), Default::default()).into()),
|
||||||
Ok(FnPtr::new_unchecked(x.name.clone(), Default::default()).into())
|
|
||||||
}
|
|
||||||
Expr::Variable(x) if (x.3).name == KEYWORD_THIS => {
|
Expr::Variable(x) if (x.3).name == KEYWORD_THIS => {
|
||||||
if let Some(val) = this_ptr {
|
if let Some(val) = this_ptr {
|
||||||
Ok(val.clone())
|
Ok(val.clone())
|
||||||
@ -1605,7 +1599,7 @@ impl Engine {
|
|||||||
|
|
||||||
// Normal function call
|
// Normal function call
|
||||||
Expr::FnCall(x, pos) if x.namespace.is_none() => {
|
Expr::FnCall(x, pos) if x.namespace.is_none() => {
|
||||||
let FnCallInfo {
|
let FnCallExpr {
|
||||||
name,
|
name,
|
||||||
native_only: native,
|
native_only: native,
|
||||||
capture: cap_scope,
|
capture: cap_scope,
|
||||||
@ -1622,9 +1616,9 @@ impl Engine {
|
|||||||
.map_err(|err| err.fill_position(*pos))
|
.map_err(|err| err.fill_position(*pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Module-qualified function call
|
// Namespace-qualified function call
|
||||||
Expr::FnCall(x, pos) if x.namespace.is_some() => {
|
Expr::FnCall(x, pos) if x.namespace.is_some() => {
|
||||||
let FnCallInfo {
|
let FnCallExpr {
|
||||||
name,
|
name,
|
||||||
namespace,
|
namespace,
|
||||||
hash,
|
hash,
|
||||||
@ -1632,9 +1626,9 @@ impl Engine {
|
|||||||
def_value,
|
def_value,
|
||||||
..
|
..
|
||||||
} = x.as_ref();
|
} = x.as_ref();
|
||||||
let modules = namespace.as_ref().map(|v| v.as_ref());
|
let namespace = namespace.as_ref().map(|v| v.as_ref());
|
||||||
self.make_qualified_function_call(
|
self.make_qualified_function_call(
|
||||||
scope, mods, state, lib, this_ptr, modules, name, args, *def_value, *hash,
|
scope, mods, state, lib, this_ptr, namespace, name, args, *def_value, *hash,
|
||||||
level,
|
level,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.fill_position(*pos))
|
.map_err(|err| err.fill_position(*pos))
|
||||||
@ -1674,6 +1668,25 @@ impl Engine {
|
|||||||
Expr::False(_) => Ok(false.into()),
|
Expr::False(_) => Ok(false.into()),
|
||||||
Expr::Unit(_) => Ok(().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::Custom(custom, _) => {
|
Expr::Custom(custom, _) => {
|
||||||
let expressions = custom
|
let expressions = custom
|
||||||
.keywords()
|
.keywords()
|
||||||
@ -2184,13 +2197,14 @@ impl Engine {
|
|||||||
// Export statement
|
// Export statement
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Export(list, _) => {
|
Stmt::Export(list, _) => {
|
||||||
for (Ident { name, pos: id_pos }, rename) in list.iter() {
|
for (IdentX { name, pos: id_pos }, rename) in list.iter() {
|
||||||
// Mark scope variables as public
|
// Mark scope variables as public
|
||||||
if let Some(index) = scope.get_index(name).map(|(i, _)| i) {
|
if let Some(index) = scope.get_index(name).map(|(i, _)| i) {
|
||||||
let alias = rename.as_ref().map(|x| &x.name).unwrap_or_else(|| name);
|
let alias = rename.as_ref().map(|x| &x.name).unwrap_or_else(|| name);
|
||||||
scope.add_entry_alias(index, alias.clone());
|
scope.add_entry_alias(index, alias.to_string());
|
||||||
} else {
|
} else {
|
||||||
return EvalAltResult::ErrorVariableNotFound(name.into(), *id_pos).into();
|
return EvalAltResult::ErrorVariableNotFound(name.to_string(), *id_pos)
|
||||||
|
.into();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Default::default())
|
Ok(Default::default())
|
||||||
|
@ -9,6 +9,7 @@ use crate::parse_error::ParseError;
|
|||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
use crate::scope::Scope;
|
use crate::scope::Scope;
|
||||||
use crate::token::{Position, NO_POS};
|
use crate::token::{Position, NO_POS};
|
||||||
|
use crate::utils::get_hasher;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -35,6 +36,7 @@ use crate::optimize::optimize_into_ast;
|
|||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
any::{type_name, TypeId},
|
any::{type_name, TypeId},
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
|
hash::{Hash, Hasher},
|
||||||
string::String,
|
string::String,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -45,6 +47,13 @@ use crate::stdlib::mem;
|
|||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf};
|
use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf};
|
||||||
|
|
||||||
|
/// Calculate a unique hash for a script.
|
||||||
|
fn calc_hash_for_scripts<'a>(scripts: impl IntoIterator<Item = &'a &'a str>) -> u64 {
|
||||||
|
let s = &mut get_hasher();
|
||||||
|
scripts.into_iter().for_each(|&script| script.hash(s));
|
||||||
|
s.finish()
|
||||||
|
}
|
||||||
|
|
||||||
/// Engine public API
|
/// Engine public API
|
||||||
impl Engine {
|
impl Engine {
|
||||||
/// Register a function of the `Engine`.
|
/// Register a function of the `Engine`.
|
||||||
@ -907,8 +916,9 @@ impl Engine {
|
|||||||
scripts: &[&str],
|
scripts: &[&str],
|
||||||
optimization_level: OptimizationLevel,
|
optimization_level: OptimizationLevel,
|
||||||
) -> Result<AST, ParseError> {
|
) -> Result<AST, ParseError> {
|
||||||
|
let hash = calc_hash_for_scripts(scripts);
|
||||||
let stream = self.lex(scripts, None);
|
let stream = self.lex(scripts, None);
|
||||||
self.parse(&mut stream.peekable(), scope, optimization_level)
|
self.parse(hash, &mut stream.peekable(), scope, optimization_level)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the contents of a file into a string.
|
/// Read the contents of a file into a string.
|
||||||
@ -1061,6 +1071,7 @@ impl Engine {
|
|||||||
.into());
|
.into());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let hash = calc_hash_for_scripts(&scripts);
|
||||||
let stream = self.lex(
|
let stream = self.lex(
|
||||||
&scripts,
|
&scripts,
|
||||||
if has_null {
|
if has_null {
|
||||||
@ -1073,8 +1084,12 @@ impl Engine {
|
|||||||
None
|
None
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
let ast =
|
let ast = self.parse_global_expr(
|
||||||
self.parse_global_expr(&mut stream.peekable(), &scope, OptimizationLevel::None)?;
|
hash,
|
||||||
|
&mut stream.peekable(),
|
||||||
|
&scope,
|
||||||
|
OptimizationLevel::None,
|
||||||
|
)?;
|
||||||
|
|
||||||
// Handle null - map to ()
|
// Handle null - map to ()
|
||||||
if has_null {
|
if has_null {
|
||||||
@ -1155,11 +1170,11 @@ impl Engine {
|
|||||||
script: &str,
|
script: &str,
|
||||||
) -> Result<AST, ParseError> {
|
) -> Result<AST, ParseError> {
|
||||||
let scripts = [script];
|
let scripts = [script];
|
||||||
|
let hash = calc_hash_for_scripts(&scripts);
|
||||||
let stream = self.lex(&scripts, None);
|
let stream = self.lex(&scripts, None);
|
||||||
{
|
|
||||||
let mut peekable = stream.peekable();
|
let mut peekable = stream.peekable();
|
||||||
self.parse_global_expr(&mut peekable, scope, self.optimization_level)
|
self.parse_global_expr(hash, &mut peekable, scope, self.optimization_level)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a script file.
|
/// Evaluate a script file.
|
||||||
@ -1316,10 +1331,12 @@ impl Engine {
|
|||||||
script: &str,
|
script: &str,
|
||||||
) -> Result<T, Box<EvalAltResult>> {
|
) -> Result<T, Box<EvalAltResult>> {
|
||||||
let scripts = [script];
|
let scripts = [script];
|
||||||
|
let hash = calc_hash_for_scripts(&scripts);
|
||||||
let stream = self.lex(&scripts, None);
|
let stream = self.lex(&scripts, None);
|
||||||
|
|
||||||
// No need to optimize a lone expression
|
// No need to optimize a lone expression
|
||||||
let ast = self.parse_global_expr(&mut stream.peekable(), scope, OptimizationLevel::None)?;
|
let ast =
|
||||||
|
self.parse_global_expr(hash, &mut stream.peekable(), scope, OptimizationLevel::None)?;
|
||||||
|
|
||||||
self.eval_ast_with_scope(scope, &ast)
|
self.eval_ast_with_scope(scope, &ast)
|
||||||
}
|
}
|
||||||
@ -1446,8 +1463,9 @@ impl Engine {
|
|||||||
script: &str,
|
script: &str,
|
||||||
) -> Result<(), Box<EvalAltResult>> {
|
) -> Result<(), Box<EvalAltResult>> {
|
||||||
let scripts = [script];
|
let scripts = [script];
|
||||||
|
let hash = calc_hash_for_scripts(&scripts);
|
||||||
let stream = self.lex(&scripts, None);
|
let stream = self.lex(&scripts, None);
|
||||||
let ast = self.parse(&mut stream.peekable(), scope, self.optimization_level)?;
|
let ast = self.parse(hash, &mut stream.peekable(), scope, self.optimization_level)?;
|
||||||
self.consume_ast_with_scope(scope, &ast)
|
self.consume_ast_with_scope(scope, &ast)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn set_max_call_levels(&mut self, levels: usize) -> &mut Self {
|
pub fn set_max_call_levels(&mut self, levels: usize) -> &mut Self {
|
||||||
self.limits_set.max_call_stack_depth = levels;
|
self.limits.max_call_stack_depth = levels;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn max_call_levels(&self) -> usize {
|
pub fn max_call_levels(&self) -> usize {
|
||||||
self.limits_set.max_call_stack_depth
|
self.limits.max_call_stack_depth
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the maximum number of operations allowed for a script to run to avoid
|
/// Set the maximum number of operations allowed for a script to run to avoid
|
||||||
@ -69,7 +69,7 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn set_max_operations(&mut self, operations: u64) -> &mut Self {
|
pub fn set_max_operations(&mut self, operations: u64) -> &mut Self {
|
||||||
self.limits_set.max_operations = if operations == u64::MAX {
|
self.limits.max_operations = if operations == u64::MAX {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
operations
|
operations
|
||||||
@ -81,7 +81,7 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn max_operations(&self) -> u64 {
|
pub fn max_operations(&self) -> u64 {
|
||||||
self.limits_set.max_operations
|
self.limits.max_operations
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the maximum number of imported modules allowed for a script.
|
/// Set the maximum number of imported modules allowed for a script.
|
||||||
@ -89,7 +89,7 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn set_max_modules(&mut self, modules: usize) -> &mut Self {
|
pub fn set_max_modules(&mut self, modules: usize) -> &mut Self {
|
||||||
self.limits_set.max_modules = modules;
|
self.limits.max_modules = modules;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn max_modules(&self) -> usize {
|
pub fn max_modules(&self) -> usize {
|
||||||
self.limits_set.max_modules
|
self.limits.max_modules
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the depth limits for expressions (0 for unlimited).
|
/// Set the depth limits for expressions (0 for unlimited).
|
||||||
@ -109,14 +109,14 @@ impl Engine {
|
|||||||
max_expr_depth: usize,
|
max_expr_depth: usize,
|
||||||
#[cfg(not(feature = "no_function"))] max_function_expr_depth: usize,
|
#[cfg(not(feature = "no_function"))] max_function_expr_depth: usize,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
self.limits_set.max_expr_depth = if max_expr_depth == usize::MAX {
|
self.limits.max_expr_depth = if max_expr_depth == usize::MAX {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
max_expr_depth
|
max_expr_depth
|
||||||
};
|
};
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
{
|
{
|
||||||
self.limits_set.max_function_expr_depth = if max_function_expr_depth == usize::MAX {
|
self.limits.max_function_expr_depth = if max_function_expr_depth == usize::MAX {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
max_function_expr_depth
|
max_function_expr_depth
|
||||||
@ -129,7 +129,7 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn max_expr_depth(&self) -> usize {
|
pub fn max_expr_depth(&self) -> usize {
|
||||||
self.limits_set.max_expr_depth
|
self.limits.max_expr_depth
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The depth limit for expressions in functions (0 for unlimited).
|
/// The depth limit for expressions in functions (0 for unlimited).
|
||||||
@ -137,14 +137,14 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn max_function_expr_depth(&self) -> usize {
|
pub fn max_function_expr_depth(&self) -> usize {
|
||||||
self.limits_set.max_function_expr_depth
|
self.limits.max_function_expr_depth
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the maximum length of strings (0 for unlimited).
|
/// Set the maximum length of strings (0 for unlimited).
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn set_max_string_size(&mut self, max_size: usize) -> &mut Self {
|
pub fn set_max_string_size(&mut self, max_size: usize) -> &mut Self {
|
||||||
self.limits_set.max_string_size = if max_size == usize::MAX { 0 } else { max_size };
|
self.limits.max_string_size = if max_size == usize::MAX { 0 } else { max_size };
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,7 +152,7 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn max_string_size(&self) -> usize {
|
pub fn max_string_size(&self) -> usize {
|
||||||
self.limits_set.max_string_size
|
self.limits.max_string_size
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the maximum length of arrays (0 for unlimited).
|
/// Set the maximum length of arrays (0 for unlimited).
|
||||||
@ -160,7 +160,7 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn set_max_array_size(&mut self, max_size: usize) -> &mut Self {
|
pub fn set_max_array_size(&mut self, max_size: usize) -> &mut Self {
|
||||||
self.limits_set.max_array_size = if max_size == usize::MAX { 0 } else { max_size };
|
self.limits.max_array_size = if max_size == usize::MAX { 0 } else { max_size };
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,7 +169,7 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn max_array_size(&self) -> usize {
|
pub fn max_array_size(&self) -> usize {
|
||||||
self.limits_set.max_array_size
|
self.limits.max_array_size
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the maximum length of object maps (0 for unlimited).
|
/// Set the maximum length of object maps (0 for unlimited).
|
||||||
@ -177,7 +177,7 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn set_max_map_size(&mut self, max_size: usize) -> &mut Self {
|
pub fn set_max_map_size(&mut self, max_size: usize) -> &mut Self {
|
||||||
self.limits_set.max_map_size = if max_size == usize::MAX { 0 } else { max_size };
|
self.limits.max_map_size = if max_size == usize::MAX { 0 } else { max_size };
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,7 +186,7 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn max_map_size(&self) -> usize {
|
pub fn max_map_size(&self) -> usize {
|
||||||
self.limits_set.max_map_size
|
self.limits.max_map_size
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the module resolution service used by the `Engine`.
|
/// Set the module resolution service used by the `Engine`.
|
||||||
|
@ -8,7 +8,7 @@ use crate::engine::{
|
|||||||
KEYWORD_PRINT, KEYWORD_TYPE_OF,
|
KEYWORD_PRINT, KEYWORD_TYPE_OF,
|
||||||
};
|
};
|
||||||
use crate::fn_native::{FnCallArgs, FnPtr};
|
use crate::fn_native::{FnCallArgs, FnPtr};
|
||||||
use crate::module::{Module, ModuleRef};
|
use crate::module::{Module, NamespaceRef};
|
||||||
use crate::optimize::OptimizationLevel;
|
use crate::optimize::OptimizationLevel;
|
||||||
use crate::parse_error::ParseErrorType;
|
use crate::parse_error::ParseErrorType;
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
@ -432,13 +432,13 @@ impl Engine {
|
|||||||
pub(crate) fn has_override_by_name_and_arguments(
|
pub(crate) fn has_override_by_name_and_arguments(
|
||||||
&self,
|
&self,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
name: &str,
|
fn_name: &str,
|
||||||
arg_types: impl AsRef<[TypeId]>,
|
arg_types: impl AsRef<[TypeId]>,
|
||||||
pub_only: bool,
|
pub_only: bool,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let arg_types = arg_types.as_ref();
|
let arg_types = arg_types.as_ref();
|
||||||
let hash_fn = calc_native_fn_hash(empty(), name, arg_types.iter().cloned());
|
let hash_fn = calc_native_fn_hash(empty(), fn_name, arg_types.iter().cloned());
|
||||||
let hash_script = calc_script_fn_hash(empty(), name, arg_types.len());
|
let hash_script = calc_script_fn_hash(empty(), fn_name, arg_types.len());
|
||||||
|
|
||||||
self.has_override(lib, hash_fn, hash_script, pub_only)
|
self.has_override(lib, hash_fn, hash_script, pub_only)
|
||||||
}
|
}
|
||||||
@ -694,7 +694,7 @@ impl Engine {
|
|||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
name: &str,
|
fn_name: &str,
|
||||||
hash_script: u64,
|
hash_script: u64,
|
||||||
target: &mut Target,
|
target: &mut Target,
|
||||||
mut call_args: StaticVec<Dynamic>,
|
mut call_args: StaticVec<Dynamic>,
|
||||||
@ -707,9 +707,9 @@ impl Engine {
|
|||||||
|
|
||||||
// Get a reference to the mutation target Dynamic
|
// Get a reference to the mutation target Dynamic
|
||||||
let obj = target.as_mut();
|
let obj = target.as_mut();
|
||||||
let mut _fn_name = name;
|
let mut fn_name = fn_name;
|
||||||
|
|
||||||
let (result, updated) = if _fn_name == KEYWORD_FN_PTR_CALL && obj.is::<FnPtr>() {
|
let (result, updated) = if fn_name == KEYWORD_FN_PTR_CALL && obj.is::<FnPtr>() {
|
||||||
// FnPtr call
|
// FnPtr call
|
||||||
let fn_ptr = obj.read_lock::<FnPtr>().unwrap();
|
let fn_ptr = obj.read_lock::<FnPtr>().unwrap();
|
||||||
// Redirect function name
|
// Redirect function name
|
||||||
@ -733,7 +733,7 @@ impl Engine {
|
|||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
mods, state, lib, fn_name, hash, args, false, false, pub_only, None, def_val, level,
|
mods, state, lib, fn_name, hash, args, false, false, pub_only, None, def_val, level,
|
||||||
)
|
)
|
||||||
} else if _fn_name == KEYWORD_FN_PTR_CALL
|
} else if fn_name == KEYWORD_FN_PTR_CALL
|
||||||
&& call_args.len() > 0
|
&& call_args.len() > 0
|
||||||
&& call_args[0].is::<FnPtr>()
|
&& call_args[0].is::<FnPtr>()
|
||||||
{
|
{
|
||||||
@ -760,7 +760,7 @@ impl Engine {
|
|||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
mods, state, lib, fn_name, hash, args, is_ref, true, pub_only, None, def_val, level,
|
mods, state, lib, fn_name, hash, args, is_ref, true, pub_only, None, def_val, level,
|
||||||
)
|
)
|
||||||
} else if _fn_name == KEYWORD_FN_PTR_CURRY && obj.is::<FnPtr>() {
|
} else if fn_name == KEYWORD_FN_PTR_CURRY && obj.is::<FnPtr>() {
|
||||||
// Curry call
|
// Curry call
|
||||||
let fn_ptr = obj.read_lock::<FnPtr>().unwrap();
|
let fn_ptr = obj.read_lock::<FnPtr>().unwrap();
|
||||||
Ok((
|
Ok((
|
||||||
@ -779,7 +779,7 @@ impl Engine {
|
|||||||
} else if {
|
} else if {
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
{
|
{
|
||||||
_fn_name == KEYWORD_IS_SHARED && call_args.is_empty()
|
fn_name == KEYWORD_IS_SHARED && call_args.is_empty()
|
||||||
}
|
}
|
||||||
#[cfg(feature = "no_closure")]
|
#[cfg(feature = "no_closure")]
|
||||||
false
|
false
|
||||||
@ -793,11 +793,11 @@ impl Engine {
|
|||||||
// Check if it is a map method call in OOP style
|
// Check if it is a map method call in OOP style
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
if let Some(map) = obj.read_lock::<Map>() {
|
if let Some(map) = obj.read_lock::<Map>() {
|
||||||
if let Some(val) = map.get(_fn_name) {
|
if let Some(val) = map.get(fn_name) {
|
||||||
if let Some(fn_ptr) = val.read_lock::<FnPtr>() {
|
if let Some(fn_ptr) = val.read_lock::<FnPtr>() {
|
||||||
// Remap the function name
|
// Remap the function name
|
||||||
_redirected = fn_ptr.get_fn_name().clone();
|
_redirected = fn_ptr.get_fn_name().clone();
|
||||||
_fn_name = &_redirected;
|
fn_name = &_redirected;
|
||||||
// Add curried arguments
|
// Add curried arguments
|
||||||
fn_ptr
|
fn_ptr
|
||||||
.curry()
|
.curry()
|
||||||
@ -809,7 +809,7 @@ impl Engine {
|
|||||||
hash = if native {
|
hash = if native {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
calc_script_fn_hash(empty(), _fn_name, call_args.len())
|
calc_script_fn_hash(empty(), fn_name, call_args.len())
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -826,8 +826,7 @@ impl Engine {
|
|||||||
let args = arg_values.as_mut();
|
let args = arg_values.as_mut();
|
||||||
|
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
mods, state, lib, _fn_name, hash, args, is_ref, true, pub_only, None, def_val,
|
mods, state, lib, fn_name, hash, args, is_ref, true, pub_only, None, def_val, level,
|
||||||
level,
|
|
||||||
)
|
)
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
@ -848,7 +847,7 @@ impl Engine {
|
|||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
name: &str,
|
fn_name: &str,
|
||||||
args_expr: impl AsRef<[Expr]>,
|
args_expr: impl AsRef<[Expr]>,
|
||||||
def_val: Option<Dynamic>,
|
def_val: Option<Dynamic>,
|
||||||
mut hash_script: u64,
|
mut hash_script: u64,
|
||||||
@ -860,8 +859,9 @@ impl Engine {
|
|||||||
let args_expr = args_expr.as_ref();
|
let args_expr = args_expr.as_ref();
|
||||||
|
|
||||||
// Handle Fn()
|
// Handle Fn()
|
||||||
if name == KEYWORD_FN_PTR && args_expr.len() == 1 {
|
if fn_name == KEYWORD_FN_PTR && args_expr.len() == 1 {
|
||||||
let hash_fn = calc_native_fn_hash(empty(), name, once(TypeId::of::<ImmutableString>()));
|
let hash_fn =
|
||||||
|
calc_native_fn_hash(empty(), fn_name, once(TypeId::of::<ImmutableString>()));
|
||||||
|
|
||||||
if !self.has_override(lib, hash_fn, hash_script, pub_only) {
|
if !self.has_override(lib, hash_fn, hash_script, pub_only) {
|
||||||
// Fn - only in function call style
|
// Fn - only in function call style
|
||||||
@ -878,7 +878,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle curry()
|
// Handle curry()
|
||||||
if name == KEYWORD_FN_PTR_CURRY && args_expr.len() > 1 {
|
if fn_name == KEYWORD_FN_PTR_CURRY && args_expr.len() > 1 {
|
||||||
let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?;
|
let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?;
|
||||||
|
|
||||||
if !fn_ptr.is::<FnPtr>() {
|
if !fn_ptr.is::<FnPtr>() {
|
||||||
@ -905,7 +905,7 @@ impl Engine {
|
|||||||
|
|
||||||
// Handle is_shared()
|
// Handle is_shared()
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
if name == KEYWORD_IS_SHARED && args_expr.len() == 1 {
|
if fn_name == KEYWORD_IS_SHARED && args_expr.len() == 1 {
|
||||||
let value = self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?;
|
let value = self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?;
|
||||||
|
|
||||||
return Ok(value.is_shared().into());
|
return Ok(value.is_shared().into());
|
||||||
@ -915,7 +915,7 @@ impl Engine {
|
|||||||
let redirected;
|
let redirected;
|
||||||
let mut args_expr = args_expr.as_ref();
|
let mut args_expr = args_expr.as_ref();
|
||||||
let mut curry = StaticVec::new();
|
let mut curry = StaticVec::new();
|
||||||
let mut name = name;
|
let mut name = fn_name;
|
||||||
|
|
||||||
if name == KEYWORD_FN_PTR_CALL
|
if name == KEYWORD_FN_PTR_CALL
|
||||||
&& args_expr.len() >= 1
|
&& args_expr.len() >= 1
|
||||||
@ -1078,7 +1078,7 @@ impl Engine {
|
|||||||
.map(|(v, _)| v)
|
.map(|(v, _)| v)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call a module-qualified function in normal function-call style.
|
/// Call a namespace-qualified function in normal function-call style.
|
||||||
/// Position in `EvalAltResult` is `None` and must be set afterwards.
|
/// Position in `EvalAltResult` is `None` and must be set afterwards.
|
||||||
pub(crate) fn make_qualified_function_call(
|
pub(crate) fn make_qualified_function_call(
|
||||||
&self,
|
&self,
|
||||||
@ -1087,8 +1087,8 @@ impl Engine {
|
|||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
modules: Option<&ModuleRef>,
|
namespace: Option<&NamespaceRef>,
|
||||||
name: &str,
|
fn_name: &str,
|
||||||
args_expr: impl AsRef<[Expr]>,
|
args_expr: impl AsRef<[Expr]>,
|
||||||
def_val: Option<bool>,
|
def_val: Option<bool>,
|
||||||
hash_script: u64,
|
hash_script: u64,
|
||||||
@ -1096,7 +1096,7 @@ impl Engine {
|
|||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
let args_expr = args_expr.as_ref();
|
let args_expr = args_expr.as_ref();
|
||||||
|
|
||||||
let modules = modules.as_ref().unwrap();
|
let namespace = namespace.as_ref().unwrap();
|
||||||
let mut arg_values: StaticVec<_>;
|
let mut arg_values: StaticVec<_>;
|
||||||
let mut first_arg_value = None;
|
let mut first_arg_value = None;
|
||||||
let mut args: StaticVec<_>;
|
let mut args: StaticVec<_>;
|
||||||
@ -1105,7 +1105,7 @@ impl Engine {
|
|||||||
// No arguments
|
// No arguments
|
||||||
args = Default::default();
|
args = Default::default();
|
||||||
} else {
|
} else {
|
||||||
// See if the first argument is a variable (not module-qualified).
|
// See if the first argument is a variable (not namespace-qualified).
|
||||||
// If so, convert to method-call style in order to leverage potential
|
// If so, convert to method-call style in order to leverage potential
|
||||||
// &mut first argument and avoid cloning the value
|
// &mut first argument and avoid cloning the value
|
||||||
if args_expr[0].get_variable_access(true).is_some() {
|
if args_expr[0].get_variable_access(true).is_some() {
|
||||||
@ -1151,7 +1151,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let module = search_imports(mods, state, modules)?;
|
let module = search_imports(mods, state, namespace)?;
|
||||||
|
|
||||||
// First search in script-defined functions (can override built-in)
|
// First search in script-defined functions (can override built-in)
|
||||||
let func = match module.get_qualified_fn(hash_script) {
|
let func = match module.get_qualified_fn(hash_script) {
|
||||||
@ -1159,7 +1159,7 @@ impl Engine {
|
|||||||
None => {
|
None => {
|
||||||
self.inc_operations(state)?;
|
self.inc_operations(state)?;
|
||||||
|
|
||||||
// Qualified Rust functions are indexed in two steps:
|
// Namespace-qualified Rust functions are indexed in two steps:
|
||||||
// 1) Calculate a hash in a similar manner to script-defined functions,
|
// 1) Calculate a hash in a similar manner to script-defined functions,
|
||||||
// i.e. qualifiers + function name + number of arguments.
|
// i.e. qualifiers + function name + number of arguments.
|
||||||
// 2) Calculate a second hash with no qualifiers, empty function name,
|
// 2) Calculate a second hash with no qualifiers, empty function name,
|
||||||
@ -1210,8 +1210,8 @@ impl Engine {
|
|||||||
None => EvalAltResult::ErrorFunctionNotFound(
|
None => EvalAltResult::ErrorFunctionNotFound(
|
||||||
format!(
|
format!(
|
||||||
"{}{} ({})",
|
"{}{} ({})",
|
||||||
modules,
|
namespace,
|
||||||
name,
|
fn_name,
|
||||||
args.iter()
|
args.iter()
|
||||||
.map(|a| if a.is::<ImmutableString>() {
|
.map(|a| if a.is::<ImmutableString>() {
|
||||||
"&str | ImmutableString | String"
|
"&str | ImmutableString | String"
|
||||||
|
11
src/lib.rs
11
src/lib.rs
@ -83,19 +83,20 @@ mod token;
|
|||||||
mod r#unsafe;
|
mod r#unsafe;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
/// The system integer type.
|
/// The system integer type. It is defined as `i64`.
|
||||||
///
|
///
|
||||||
/// If the `only_i32` feature is enabled, this will be `i32` instead.
|
/// If the `only_i32` feature is enabled, this will be `i32` instead.
|
||||||
#[cfg(not(feature = "only_i32"))]
|
#[cfg(not(feature = "only_i32"))]
|
||||||
pub type INT = i64;
|
pub type INT = i64;
|
||||||
|
|
||||||
/// The system integer type.
|
/// The system integer type.
|
||||||
|
/// It is defined as `i32` since the `only_i32` feature is used.
|
||||||
///
|
///
|
||||||
/// If the `only_i32` feature is not enabled, this will be `i64` instead.
|
/// If the `only_i32` feature is not enabled, this will be `i64` instead.
|
||||||
#[cfg(feature = "only_i32")]
|
#[cfg(feature = "only_i32")]
|
||||||
pub type INT = i32;
|
pub type INT = i32;
|
||||||
|
|
||||||
/// The system floating-point type.
|
/// The system floating-point type. It is defined as `f64`.
|
||||||
///
|
///
|
||||||
/// Not available under the `no_float` feature.
|
/// Not available under the `no_float` feature.
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
@ -103,6 +104,7 @@ pub type INT = i32;
|
|||||||
pub type FLOAT = f64;
|
pub type FLOAT = f64;
|
||||||
|
|
||||||
/// The system floating-point type.
|
/// The system floating-point type.
|
||||||
|
/// It is defined as `f32` since the `f32_float` feature is used.
|
||||||
///
|
///
|
||||||
/// Not available under the `no_float` feature.
|
/// Not available under the `no_float` feature.
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
@ -168,8 +170,7 @@ pub use token::{get_next_token, parse_string_literal, InputStream, Token, Tokeni
|
|||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
#[deprecated(note = "this type is volatile and may change")]
|
#[deprecated(note = "this type is volatile and may change")]
|
||||||
pub use ast::{
|
pub use ast::{
|
||||||
BinaryExpr, CustomExpr, Expr, FloatWrapper, FnCallInfo, Ident, IdentX, ReturnType, ScriptFnDef,
|
BinaryExpr, CustomExpr, Expr, FnCallExpr, Ident, IdentX, ReturnType, ScriptFnDef, Stmt,
|
||||||
Stmt,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
@ -178,7 +179,7 @@ pub use engine::{Imports, Limits, State as EvalState};
|
|||||||
|
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
#[deprecated(note = "this type is volatile and may change")]
|
#[deprecated(note = "this type is volatile and may change")]
|
||||||
pub use module::ModuleRef;
|
pub use module::NamespaceRef;
|
||||||
|
|
||||||
/// _[INTERNALS]_ Alias to [`smallvec::SmallVec<[T; 4]>`](https://crates.io/crates/smallvec),
|
/// _[INTERNALS]_ Alias to [`smallvec::SmallVec<[T; 4]>`](https://crates.io/crates/smallvec),
|
||||||
/// which is a specialized `Vec` backed by a small, fixed-size array when there are <= 4 items stored.
|
/// which is a specialized `Vec` backed by a small, fixed-size array when there are <= 4 items stored.
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
//! Module defining external-loaded modules for Rhai.
|
//! Module defining external-loaded modules for Rhai.
|
||||||
|
|
||||||
use crate::ast::{FnAccess, Ident};
|
use crate::ast::{FnAccess, IdentX};
|
||||||
use crate::dynamic::{Dynamic, Variant};
|
use crate::dynamic::{Dynamic, Variant};
|
||||||
use crate::fn_native::{
|
use crate::fn_native::{
|
||||||
shared_make_mut, shared_take_or_clone, CallableFunction, FnCallArgs, IteratorFn,
|
shared_make_mut, shared_take_or_clone, CallableFunction, FnCallArgs, IteratorFn,
|
||||||
@ -55,8 +55,8 @@ pub struct FuncInfo {
|
|||||||
pub types: Option<StaticVec<TypeId>>,
|
pub types: Option<StaticVec<TypeId>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An imported module, which may contain variables, sub-modules,
|
/// A module which may contain variables, sub-modules, external Rust functions,
|
||||||
/// external Rust functions, and script-defined functions.
|
/// and/or script-defined functions.
|
||||||
///
|
///
|
||||||
/// Not available under the `no_module` feature.
|
/// Not available under the `no_module` feature.
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
@ -253,7 +253,7 @@ impl Module {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to a modules-qualified variable.
|
/// Get a reference to a namespace-qualified variable.
|
||||||
/// Name and Position in `EvalAltResult` are None and must be set afterwards.
|
/// Name and Position in `EvalAltResult` are None and must be set afterwards.
|
||||||
///
|
///
|
||||||
/// The `u64` hash is calculated by the function `crate::calc_native_fn_hash`.
|
/// The `u64` hash is calculated by the function `crate::calc_native_fn_hash`.
|
||||||
@ -1105,7 +1105,7 @@ impl Module {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a modules-qualified function.
|
/// Get a namespace-qualified function.
|
||||||
/// Name and Position in `EvalAltResult` are None and must be set afterwards.
|
/// Name and Position in `EvalAltResult` are None and must be set afterwards.
|
||||||
///
|
///
|
||||||
/// The `u64` hash is calculated by the function `crate::calc_native_fn_hash` and must match
|
/// The `u64` hash is calculated by the function `crate::calc_native_fn_hash` and must match
|
||||||
@ -1435,7 +1435,7 @@ impl Module {
|
|||||||
if let Some(param_types) = types {
|
if let Some(param_types) = types {
|
||||||
assert_eq!(*params, param_types.len());
|
assert_eq!(*params, param_types.len());
|
||||||
|
|
||||||
// Qualified Rust functions are indexed in two steps:
|
// Namespace-qualified Rust functions are indexed in two steps:
|
||||||
// 1) Calculate a hash in a similar manner to script-defined functions,
|
// 1) Calculate a hash in a similar manner to script-defined functions,
|
||||||
// i.e. qualifiers + function name + number of arguments.
|
// i.e. qualifiers + function name + number of arguments.
|
||||||
let hash_qualified_script =
|
let hash_qualified_script =
|
||||||
@ -1516,21 +1516,21 @@ impl Module {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// _[INTERNALS]_ A chain of module names to qualify a variable or function call.
|
/// _[INTERNALS]_ A chain of module names to namespace-qualify a variable or function call.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
///
|
///
|
||||||
/// A `u64` hash key is cached for quick search purposes.
|
/// A `u64` hash key is cached for quick search purposes.
|
||||||
///
|
///
|
||||||
/// A `StaticVec` is used because most module-level access contains only one level,
|
/// A `StaticVec` is used because most namespace-qualified access contains only one level,
|
||||||
/// and it is wasteful to always allocate a `Vec` with one element.
|
/// and it is wasteful to always allocate a `Vec` with one element.
|
||||||
///
|
///
|
||||||
/// ## WARNING
|
/// ## WARNING
|
||||||
///
|
///
|
||||||
/// This type is volatile and may change.
|
/// This type is volatile and may change.
|
||||||
#[derive(Clone, Eq, PartialEq, Default, Hash)]
|
#[derive(Clone, Eq, PartialEq, Default, Hash)]
|
||||||
pub struct ModuleRef(StaticVec<Ident>, Option<NonZeroUsize>);
|
pub struct NamespaceRef(StaticVec<IdentX>, Option<NonZeroUsize>);
|
||||||
|
|
||||||
impl fmt::Debug for ModuleRef {
|
impl fmt::Debug for NamespaceRef {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
fmt::Debug::fmt(&self.0, f)?;
|
fmt::Debug::fmt(&self.0, f)?;
|
||||||
|
|
||||||
@ -1542,31 +1542,31 @@ impl fmt::Debug for ModuleRef {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for ModuleRef {
|
impl Deref for NamespaceRef {
|
||||||
type Target = StaticVec<Ident>;
|
type Target = StaticVec<IdentX>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DerefMut for ModuleRef {
|
impl DerefMut for NamespaceRef {
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
&mut self.0
|
&mut self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ModuleRef {
|
impl fmt::Display for NamespaceRef {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
for Ident { name, .. } in self.0.iter() {
|
for IdentX { name, .. } in self.0.iter() {
|
||||||
write!(f, "{}{}", name, Token::DoubleColon.syntax())?;
|
write!(f, "{}{}", name, Token::DoubleColon.syntax())?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<StaticVec<Ident>> for ModuleRef {
|
impl From<StaticVec<IdentX>> for NamespaceRef {
|
||||||
fn from(modules: StaticVec<Ident>) -> Self {
|
fn from(modules: StaticVec<IdentX>) -> Self {
|
||||||
Self(modules, None)
|
Self(modules, None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1596,7 +1596,7 @@ impl<M: Into<Module>> AddAssign<M> for Module {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModuleRef {
|
impl NamespaceRef {
|
||||||
pub(crate) fn index(&self) -> Option<NonZeroUsize> {
|
pub(crate) fn index(&self) -> Option<NonZeroUsize> {
|
||||||
self.1
|
self.1
|
||||||
}
|
}
|
||||||
|
652
src/optimize.rs
652
src/optimize.rs
@ -1,6 +1,6 @@
|
|||||||
//! Module implementing the AST optimizer.
|
//! Module implementing the AST optimizer.
|
||||||
|
|
||||||
use crate::ast::{BinaryExpr, CustomExpr, Expr, FnCallInfo, ScriptFnDef, Stmt, AST};
|
use crate::ast::{Expr, ScriptFnDef, Stmt, AST};
|
||||||
use crate::dynamic::Dynamic;
|
use crate::dynamic::Dynamic;
|
||||||
use crate::engine::{
|
use crate::engine::{
|
||||||
Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_IS_DEF_FN, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT,
|
Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_IS_DEF_FN, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT,
|
||||||
@ -10,7 +10,8 @@ use crate::fn_call::run_builtin_binary_op;
|
|||||||
use crate::module::Module;
|
use crate::module::Module;
|
||||||
use crate::parser::map_dynamic_to_expr;
|
use crate::parser::map_dynamic_to_expr;
|
||||||
use crate::scope::Scope;
|
use crate::scope::Scope;
|
||||||
use crate::token::{is_valid_identifier, NO_POS};
|
use crate::token::{is_valid_identifier, Position, NO_POS};
|
||||||
|
use crate::utils::get_hasher;
|
||||||
use crate::{calc_native_fn_hash, StaticVec};
|
use crate::{calc_native_fn_hash, StaticVec};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
@ -18,7 +19,9 @@ use crate::ast::ReturnType;
|
|||||||
|
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
|
hash::{Hash, Hasher},
|
||||||
iter::empty,
|
iter::empty,
|
||||||
|
mem,
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
vec,
|
vec,
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
@ -154,158 +157,45 @@ fn call_fn_with_constant_arguments(
|
|||||||
.map(|(v, _)| v)
|
.map(|(v, _)| v)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Optimize a statement.
|
/// Optimize a block of statements.
|
||||||
fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
fn optimize_stmt_block(
|
||||||
match stmt {
|
mut statements: Vec<Stmt>,
|
||||||
// expr op= expr
|
pos: Position,
|
||||||
Stmt::Assignment(x, pos) => match x.0 {
|
state: &mut State,
|
||||||
Expr::Variable(_) => {
|
preserve_result: bool,
|
||||||
Stmt::Assignment(Box::new((x.0, x.1, optimize_expr(x.2, state))), pos)
|
count_promote_as_dirty: bool,
|
||||||
}
|
) -> Stmt {
|
||||||
_ => Stmt::Assignment(
|
|
||||||
Box::new((optimize_expr(x.0, state), x.1, optimize_expr(x.2, state))),
|
|
||||||
pos,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
// if false { if_block } -> Noop
|
|
||||||
Stmt::IfThenElse(Expr::False(pos), x, _) if x.1.is_none() => {
|
|
||||||
state.set_dirty();
|
|
||||||
Stmt::Noop(pos)
|
|
||||||
}
|
|
||||||
// if true { if_block } -> if_block
|
|
||||||
Stmt::IfThenElse(Expr::True(_), x, _) if x.1.is_none() => optimize_stmt(x.0, state, true),
|
|
||||||
// if expr { Noop }
|
|
||||||
Stmt::IfThenElse(condition, x, _) if x.1.is_none() && matches!(x.0, Stmt::Noop(_)) => {
|
|
||||||
state.set_dirty();
|
|
||||||
|
|
||||||
let pos = condition.position();
|
|
||||||
let expr = optimize_expr(condition, state);
|
|
||||||
|
|
||||||
if preserve_result {
|
|
||||||
// -> { expr, Noop }
|
|
||||||
let mut statements = Vec::new();
|
|
||||||
statements.push(Stmt::Expr(expr));
|
|
||||||
statements.push(x.0);
|
|
||||||
|
|
||||||
Stmt::Block(statements, pos)
|
|
||||||
} else {
|
|
||||||
// -> expr
|
|
||||||
Stmt::Expr(expr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if expr { if_block }
|
|
||||||
Stmt::IfThenElse(condition, x, pos) if x.1.is_none() => Stmt::IfThenElse(
|
|
||||||
optimize_expr(condition, state),
|
|
||||||
Box::new((optimize_stmt(x.0, state, true), None)),
|
|
||||||
pos,
|
|
||||||
),
|
|
||||||
// if false { if_block } else { else_block } -> else_block
|
|
||||||
Stmt::IfThenElse(Expr::False(_), x, _) if x.1.is_some() => {
|
|
||||||
optimize_stmt(x.1.unwrap(), state, true)
|
|
||||||
}
|
|
||||||
// if true { if_block } else { else_block } -> if_block
|
|
||||||
Stmt::IfThenElse(Expr::True(_), x, _) => optimize_stmt(x.0, state, true),
|
|
||||||
// if expr { if_block } else { else_block }
|
|
||||||
Stmt::IfThenElse(condition, x, pos) => Stmt::IfThenElse(
|
|
||||||
optimize_expr(condition, state),
|
|
||||||
Box::new((
|
|
||||||
optimize_stmt(x.0, state, true),
|
|
||||||
match optimize_stmt(x.1.unwrap(), state, true) {
|
|
||||||
Stmt::Noop(_) => None, // Noop -> no else block
|
|
||||||
stmt => Some(stmt),
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
pos,
|
|
||||||
),
|
|
||||||
|
|
||||||
// while false { block } -> Noop
|
|
||||||
Stmt::While(Expr::False(pos), _, _) => {
|
|
||||||
state.set_dirty();
|
|
||||||
Stmt::Noop(pos)
|
|
||||||
}
|
|
||||||
// while true { block } -> loop { block }
|
|
||||||
Stmt::While(Expr::True(_), block, pos) => {
|
|
||||||
Stmt::Loop(Box::new(optimize_stmt(*block, state, false)), pos)
|
|
||||||
}
|
|
||||||
// while expr { block }
|
|
||||||
Stmt::While(condition, block, pos) => {
|
|
||||||
match optimize_stmt(*block, state, false) {
|
|
||||||
// while expr { break; } -> { expr; }
|
|
||||||
Stmt::Break(pos) => {
|
|
||||||
// Only a single break statement - turn into running the guard expression once
|
|
||||||
state.set_dirty();
|
|
||||||
let mut statements = Vec::new();
|
|
||||||
statements.push(Stmt::Expr(optimize_expr(condition, state)));
|
|
||||||
if preserve_result {
|
|
||||||
statements.push(Stmt::Noop(pos))
|
|
||||||
}
|
|
||||||
Stmt::Block(statements, pos)
|
|
||||||
}
|
|
||||||
// while expr { block }
|
|
||||||
stmt => Stmt::While(optimize_expr(condition, state), Box::new(stmt), pos),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// loop { block }
|
|
||||||
Stmt::Loop(block, pos) => match optimize_stmt(*block, state, false) {
|
|
||||||
// loop { break; } -> Noop
|
|
||||||
Stmt::Break(pos) => {
|
|
||||||
// Only a single break statement
|
|
||||||
state.set_dirty();
|
|
||||||
Stmt::Noop(pos)
|
|
||||||
}
|
|
||||||
// loop { block }
|
|
||||||
stmt => Stmt::Loop(Box::new(stmt), pos),
|
|
||||||
},
|
|
||||||
// for id in expr { block }
|
|
||||||
Stmt::For(iterable, x, pos) => {
|
|
||||||
let (var_name, block) = *x;
|
|
||||||
Stmt::For(
|
|
||||||
optimize_expr(iterable, state),
|
|
||||||
Box::new((var_name, optimize_stmt(block, state, false))),
|
|
||||||
pos,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// let id = expr;
|
|
||||||
Stmt::Let(name, Some(expr), export, pos) => {
|
|
||||||
Stmt::Let(name, Some(optimize_expr(expr, state)), export, pos)
|
|
||||||
}
|
|
||||||
// let id;
|
|
||||||
stmt @ Stmt::Let(_, None, _, _) => stmt,
|
|
||||||
// import expr as var;
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
Stmt::Import(expr, alias, pos) => Stmt::Import(optimize_expr(expr, state), alias, pos),
|
|
||||||
// { block }
|
|
||||||
Stmt::Block(statements, pos) => {
|
|
||||||
let orig_len = statements.len(); // Original number of statements in the block, for change detection
|
let orig_len = statements.len(); // Original number of statements in the block, for change detection
|
||||||
let orig_constants_len = state.constants.len(); // Original number of constants in the state, for restore later
|
let orig_constants_len = state.constants.len(); // Original number of constants in the state, for restore later
|
||||||
|
|
||||||
// Optimize each statement in the block
|
// Optimize each statement in the block
|
||||||
let mut result: Vec<_> = statements
|
statements.iter_mut().for_each(|stmt| match stmt {
|
||||||
.into_iter()
|
|
||||||
.map(|stmt| match stmt {
|
|
||||||
// Add constant literals into the state
|
// Add constant literals into the state
|
||||||
Stmt::Const(var_def, Some(expr), _, pos) if expr.is_literal() => {
|
Stmt::Const(var_def, Some(expr), _, pos) if expr.is_constant() => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
state.push_constant(&var_def.name, expr);
|
state.push_constant(&var_def.name, mem::take(expr));
|
||||||
Stmt::Noop(pos) // No need to keep constants
|
*stmt = Stmt::Noop(*pos); // No need to keep constants
|
||||||
}
|
}
|
||||||
Stmt::Const(var_def, None, _, pos) => {
|
Stmt::Const(var_def, None, _, pos) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
state.push_constant(&var_def.name, Expr::Unit(var_def.pos));
|
state.push_constant(&var_def.name, Expr::Unit(var_def.pos));
|
||||||
Stmt::Noop(pos) // No need to keep constants
|
*stmt = Stmt::Noop(*pos); // No need to keep constants
|
||||||
}
|
}
|
||||||
// Optimize the statement
|
// Optimize the statement
|
||||||
stmt => optimize_stmt(stmt, state, preserve_result),
|
_ => optimize_stmt(stmt, state, preserve_result),
|
||||||
})
|
});
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Remove all raw expression statements that are pure except for the very last statement
|
// Remove all raw expression statements that are pure except for the very last statement
|
||||||
let last_stmt = if preserve_result { result.pop() } else { None };
|
let last_stmt = if preserve_result {
|
||||||
|
statements.pop()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
result.retain(|stmt| !stmt.is_pure());
|
statements.retain(|stmt| !stmt.is_pure());
|
||||||
|
|
||||||
if let Some(stmt) = last_stmt {
|
if let Some(stmt) = last_stmt {
|
||||||
result.push(stmt);
|
statements.push(stmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove all let/import statements at the end of a block - the new variables will go away anyway.
|
// Remove all let/import statements at the end of a block - the new variables will go away anyway.
|
||||||
@ -313,15 +203,13 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
|||||||
// otherwise there may be side effects.
|
// otherwise there may be side effects.
|
||||||
let mut removed = false;
|
let mut removed = false;
|
||||||
|
|
||||||
while let Some(expr) = result.pop() {
|
while let Some(expr) = statements.pop() {
|
||||||
match expr {
|
match expr {
|
||||||
Stmt::Let(_, expr, _, _) => {
|
Stmt::Let(_, expr, _, _) => removed = expr.as_ref().map(Expr::is_pure).unwrap_or(true),
|
||||||
removed = expr.as_ref().map(Expr::is_pure).unwrap_or(true)
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Import(expr, _, _) => removed = expr.is_pure(),
|
Stmt::Import(expr, _, _) => removed = expr.is_pure(),
|
||||||
_ => {
|
_ => {
|
||||||
result.push(expr);
|
statements.push(expr);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -329,23 +217,21 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
|||||||
|
|
||||||
if preserve_result {
|
if preserve_result {
|
||||||
if removed {
|
if removed {
|
||||||
result.push(Stmt::Noop(pos))
|
statements.push(Stmt::Noop(pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optimize all the statements again
|
// Optimize all the statements again
|
||||||
result = result
|
let num_statements = statements.len();
|
||||||
.into_iter()
|
statements
|
||||||
.rev()
|
.iter_mut()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, stmt)| optimize_stmt(stmt, state, i == 0))
|
.for_each(|(i, stmt)| optimize_stmt(stmt, state, i == num_statements));
|
||||||
.rev()
|
|
||||||
.collect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove everything following the the first return/throw
|
// Remove everything following the the first return/throw
|
||||||
let mut dead_code = false;
|
let mut dead_code = false;
|
||||||
|
|
||||||
result.retain(|stmt| {
|
statements.retain(|stmt| {
|
||||||
if dead_code {
|
if dead_code {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -359,80 +245,205 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Change detection
|
// Change detection
|
||||||
if orig_len != result.len() {
|
if orig_len != statements.len() {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pop the stack and remove all the local constants
|
// Pop the stack and remove all the local constants
|
||||||
state.restore_constants(orig_constants_len);
|
state.restore_constants(orig_constants_len);
|
||||||
|
|
||||||
match &result[..] {
|
match &statements[..] {
|
||||||
// No statements in block - change to No-op
|
// No statements in block - change to No-op
|
||||||
[] => {
|
[] => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
Stmt::Noop(pos)
|
Stmt::Noop(pos)
|
||||||
}
|
}
|
||||||
// Only one let statement - leave it alone
|
// Only one let statement - leave it alone
|
||||||
[x] if matches!(x, Stmt::Let(_, _, _, _)) => Stmt::Block(result, pos),
|
[x] if matches!(x, Stmt::Let(_, _, _, _)) => Stmt::Block(statements, pos),
|
||||||
// Only one import statement - leave it alone
|
// Only one import statement - leave it alone
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
[x] if matches!(x, Stmt::Import(_, _, _)) => Stmt::Block(result, pos),
|
[x] if matches!(x, Stmt::Import(_, _, _)) => Stmt::Block(statements, pos),
|
||||||
// Only one statement - promote
|
// Only one statement - promote
|
||||||
[_] => {
|
[_] => {
|
||||||
|
if count_promote_as_dirty {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
result.remove(0)
|
|
||||||
}
|
}
|
||||||
_ => Stmt::Block(result, pos),
|
statements.remove(0)
|
||||||
}
|
}
|
||||||
|
_ => Stmt::Block(statements, pos),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Optimize a statement.
|
||||||
|
fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
||||||
|
match stmt {
|
||||||
|
// expr op= expr
|
||||||
|
Stmt::Assignment(ref mut x, _) => match x.0 {
|
||||||
|
Expr::Variable(_) => optimize_expr(&mut x.2, state),
|
||||||
|
_ => {
|
||||||
|
optimize_expr(&mut x.0, state);
|
||||||
|
optimize_expr(&mut x.2, state);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// if false { if_block } -> Noop
|
||||||
|
Stmt::IfThenElse(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 = 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(_)) =>
|
||||||
|
{
|
||||||
|
state.set_dirty();
|
||||||
|
|
||||||
|
let pos = condition.position();
|
||||||
|
let mut expr = mem::take(condition);
|
||||||
|
optimize_expr(&mut expr, state);
|
||||||
|
|
||||||
|
*stmt = if preserve_result {
|
||||||
|
// -> { expr, Noop }
|
||||||
|
let mut statements = Vec::new();
|
||||||
|
statements.push(Stmt::Expr(expr));
|
||||||
|
statements.push(mem::take(&mut x.0));
|
||||||
|
Stmt::Block(statements, pos)
|
||||||
|
} else {
|
||||||
|
// -> expr
|
||||||
|
Stmt::Expr(expr)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// if expr { if_block }
|
||||||
|
Stmt::IfThenElse(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 = 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 = 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, _) => {
|
||||||
|
optimize_expr(condition, state);
|
||||||
|
optimize_stmt(&mut x.0, state, true);
|
||||||
|
if let Some(else_block) = x.1.as_mut() {
|
||||||
|
optimize_stmt(else_block, state, true);
|
||||||
|
match else_block {
|
||||||
|
Stmt::Noop(_) => x.1 = None, // Noop -> no else block
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// while false { block } -> Noop
|
||||||
|
Stmt::While(Expr::False(pos), _, _) => {
|
||||||
|
state.set_dirty();
|
||||||
|
*stmt = Stmt::Noop(*pos)
|
||||||
|
}
|
||||||
|
// while true { block } -> loop { block }
|
||||||
|
Stmt::While(Expr::True(_), block, pos) => {
|
||||||
|
optimize_stmt(block, state, false);
|
||||||
|
*stmt = Stmt::Loop(Box::new(mem::take(block)), *pos)
|
||||||
|
}
|
||||||
|
// while expr { block }
|
||||||
|
Stmt::While(condition, block, _) => {
|
||||||
|
optimize_stmt(block, state, false);
|
||||||
|
optimize_expr(condition, state);
|
||||||
|
|
||||||
|
match **block {
|
||||||
|
// while expr { break; } -> { expr; }
|
||||||
|
Stmt::Break(pos) => {
|
||||||
|
// Only a single break statement - turn into running the guard expression once
|
||||||
|
state.set_dirty();
|
||||||
|
let mut statements = Vec::new();
|
||||||
|
statements.push(Stmt::Expr(mem::take(condition)));
|
||||||
|
if preserve_result {
|
||||||
|
statements.push(Stmt::Noop(pos))
|
||||||
|
}
|
||||||
|
*stmt = Stmt::Block(statements, pos);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// loop { block }
|
||||||
|
Stmt::Loop(block, _) => {
|
||||||
|
optimize_stmt(block, state, false);
|
||||||
|
|
||||||
|
match **block {
|
||||||
|
// loop { break; } -> Noop
|
||||||
|
Stmt::Break(pos) => {
|
||||||
|
// Only a single break statement
|
||||||
|
state.set_dirty();
|
||||||
|
*stmt = Stmt::Noop(pos)
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// for id in expr { block }
|
||||||
|
Stmt::For(ref mut iterable, ref mut x, _) => {
|
||||||
|
optimize_expr(iterable, state);
|
||||||
|
optimize_stmt(&mut x.1, state, false);
|
||||||
|
}
|
||||||
|
// let id = expr;
|
||||||
|
Stmt::Let(_, Some(ref mut expr), _, _) => optimize_expr(expr, state),
|
||||||
|
// let id;
|
||||||
|
Stmt::Let(_, None, _, _) => (),
|
||||||
|
// import expr as var;
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
Stmt::Import(ref mut expr, _, _) => optimize_expr(expr, state),
|
||||||
|
// { block }
|
||||||
|
Stmt::Block(statements, pos) => {
|
||||||
|
*stmt = optimize_stmt_block(mem::take(statements), *pos, state, preserve_result, true);
|
||||||
}
|
}
|
||||||
// try { block } catch ( var ) { block }
|
// try { block } catch ( var ) { block }
|
||||||
Stmt::TryCatch(x, _, _) if x.0.is_pure() => {
|
Stmt::TryCatch(x, _, _) if x.0.is_pure() => {
|
||||||
// If try block is pure, there will never be any exceptions
|
// If try block is pure, there will never be any exceptions
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
let pos = x.0.position();
|
let pos = x.0.position();
|
||||||
let mut statements = match optimize_stmt(x.0, state, preserve_result) {
|
optimize_stmt(&mut x.0, state, preserve_result);
|
||||||
|
let mut statements = match mem::take(&mut x.0) {
|
||||||
Stmt::Block(statements, _) => statements,
|
Stmt::Block(statements, _) => statements,
|
||||||
stmt => vec![stmt],
|
stmt => vec![stmt],
|
||||||
};
|
};
|
||||||
statements.push(Stmt::Noop(pos));
|
statements.push(Stmt::Noop(pos));
|
||||||
Stmt::Block(statements, pos)
|
*stmt = Stmt::Block(statements, pos);
|
||||||
}
|
}
|
||||||
// try { block } catch ( var ) { block }
|
// try { block } catch ( var ) { block }
|
||||||
Stmt::TryCatch(x, try_pos, catch_pos) => {
|
Stmt::TryCatch(ref mut x, _, _) => {
|
||||||
let (try_block, var_name, catch_block) = *x;
|
optimize_stmt(&mut x.0, state, false);
|
||||||
Stmt::TryCatch(
|
optimize_stmt(&mut x.2, state, false);
|
||||||
Box::new((
|
|
||||||
optimize_stmt(try_block, state, false),
|
|
||||||
var_name,
|
|
||||||
optimize_stmt(catch_block, state, false),
|
|
||||||
)),
|
|
||||||
try_pos,
|
|
||||||
catch_pos,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
// {}
|
// {}
|
||||||
Stmt::Expr(Expr::Stmt(x, pos)) if x.is_empty() => {
|
Stmt::Expr(Expr::Stmt(x, pos)) if x.is_empty() => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
Stmt::Noop(pos)
|
*stmt = Stmt::Noop(*pos);
|
||||||
}
|
}
|
||||||
// {...};
|
// {...};
|
||||||
Stmt::Expr(Expr::Stmt(x, pos)) => {
|
Stmt::Expr(Expr::Stmt(x, pos)) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
Stmt::Block(x.into_vec(), pos)
|
*stmt = Stmt::Block(mem::take(x).into_vec(), *pos);
|
||||||
}
|
}
|
||||||
// expr;
|
// expr;
|
||||||
Stmt::Expr(expr) => Stmt::Expr(optimize_expr(expr, state)),
|
Stmt::Expr(ref mut expr) => optimize_expr(expr, state),
|
||||||
// return expr;
|
// return expr;
|
||||||
Stmt::ReturnWithVal(ret, Some(expr), pos) => {
|
Stmt::ReturnWithVal(_, Some(ref mut expr), _) => optimize_expr(expr, state),
|
||||||
Stmt::ReturnWithVal(ret, Some(optimize_expr(expr, state)), pos)
|
|
||||||
}
|
|
||||||
// All other statements - skip
|
// All other statements - skip
|
||||||
stmt => stmt,
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Optimize an expression.
|
/// Optimize an expression.
|
||||||
fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
||||||
// These keywords are handled specially
|
// These keywords are handled specially
|
||||||
const DONT_EVAL_KEYWORDS: &[&str] = &[
|
const DONT_EVAL_KEYWORDS: &[&str] = &[
|
||||||
KEYWORD_PRINT, // side effects
|
KEYWORD_PRINT, // side effects
|
||||||
@ -444,296 +455,289 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
|||||||
|
|
||||||
match expr {
|
match expr {
|
||||||
// expr - do not promote because there is a reason it is wrapped in an `Expr::Expr`
|
// expr - do not promote because there is a reason it is wrapped in an `Expr::Expr`
|
||||||
Expr::Expr(x) => Expr::Expr(Box::new(optimize_expr(*x, state))),
|
Expr::Expr(x) => optimize_expr(x, state),
|
||||||
// {}
|
// {}
|
||||||
Expr::Stmt(x, pos) if x.is_empty() => {
|
Expr::Stmt(x, pos) if x.is_empty() => { state.set_dirty(); *expr = Expr::Unit(*pos) }
|
||||||
state.set_dirty();
|
// { stmt; ... } - do not count promotion as dirty because it gets turned back into an array
|
||||||
Expr::Unit(pos)
|
Expr::Stmt(x, pos) => match optimize_stmt_block(mem::take(x).into_vec(), *pos, state, true, false) {
|
||||||
}
|
// {}
|
||||||
|
Stmt::Noop(_) => { state.set_dirty(); *expr = Expr::Unit(*pos); }
|
||||||
|
// { stmt, .. }
|
||||||
|
Stmt::Block(statements, _) => *x = Box::new(statements.into()),
|
||||||
|
// { expr }
|
||||||
|
Stmt::Expr(inner) => { state.set_dirty(); *expr = inner; }
|
||||||
// { stmt }
|
// { stmt }
|
||||||
Expr::Stmt(mut x, pos) if x.len() == 1 => match x.pop().unwrap() {
|
stmt => x.push(stmt),
|
||||||
// {} -> ()
|
|
||||||
Stmt::Noop(_) => {
|
|
||||||
state.set_dirty();
|
|
||||||
Expr::Unit(pos)
|
|
||||||
}
|
}
|
||||||
// { expr } -> expr
|
|
||||||
Stmt::Expr(expr) => {
|
|
||||||
state.set_dirty();
|
|
||||||
optimize_expr(expr, state)
|
|
||||||
}
|
|
||||||
// { stmt }
|
|
||||||
stmt => Expr::Stmt(Box::new(vec![optimize_stmt(stmt, state, true)].into()), pos)
|
|
||||||
}
|
|
||||||
// { stmt; ... }
|
|
||||||
Expr::Stmt(x, pos) => Expr::Stmt(Box::new(
|
|
||||||
x.into_iter().map(|stmt| optimize_stmt(stmt, state, true)).collect(),
|
|
||||||
), pos),
|
|
||||||
|
|
||||||
// lhs.rhs
|
// lhs.rhs
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Dot(x, dot_pos) => match (x.lhs, x.rhs) {
|
Expr::Dot(x, _) => match (&mut x.lhs, &mut x.rhs) {
|
||||||
// map.string
|
// map.string
|
||||||
(Expr::Map(m, pos), Expr::Property(p)) if m.iter().all(|(_, x)| x.is_pure()) => {
|
(Expr::Map(m, pos), Expr::Property(p)) if m.iter().all(|(_, x)| x.is_pure()) => {
|
||||||
let prop = &p.1.name;
|
let prop = &p.1.name;
|
||||||
// Map literal where everything is pure - promote the indexed item.
|
// Map literal where everything is pure - promote the indexed item.
|
||||||
// All other items can be thrown away.
|
// All other items can be thrown away.
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
m.into_iter().find(|(x, _)| &x.name == prop)
|
*expr = mem::take(m).into_iter().find(|(x, _)| &x.name == prop)
|
||||||
.map(|(_, mut expr)| { expr.set_position(pos); expr })
|
.map(|(_, mut expr)| { expr.set_position(*pos); expr })
|
||||||
.unwrap_or_else(|| Expr::Unit(pos))
|
.unwrap_or_else(|| Expr::Unit(*pos));
|
||||||
}
|
}
|
||||||
// var.rhs
|
// var.rhs
|
||||||
(lhs @ Expr::Variable(_), rhs) => Expr::Dot(Box::new(BinaryExpr {
|
(Expr::Variable(_), rhs) => optimize_expr(rhs, state),
|
||||||
lhs,
|
|
||||||
rhs: optimize_expr(rhs, state),
|
|
||||||
}), dot_pos),
|
|
||||||
// lhs.rhs
|
// lhs.rhs
|
||||||
(lhs, rhs) => Expr::Dot(Box::new(BinaryExpr {
|
(lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); }
|
||||||
lhs: optimize_expr(lhs, state),
|
|
||||||
rhs: optimize_expr(rhs, state),
|
|
||||||
}), dot_pos)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// lhs[rhs]
|
// lhs[rhs]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(x, idx_pos) => match (x.lhs, x.rhs) {
|
Expr::Index(x, _) => match (&mut x.lhs, &mut x.rhs) {
|
||||||
// array[int]
|
// array[int]
|
||||||
(Expr::Array(mut a, pos), Expr::IntegerConstant(i, _))
|
(Expr::Array(a, pos), Expr::IntegerConstant(i, _))
|
||||||
if i >= 0 && (i as usize) < a.len() && a.iter().all(Expr::is_pure) =>
|
if *i >= 0 && (*i as usize) < a.len() && a.iter().all(Expr::is_pure) =>
|
||||||
{
|
{
|
||||||
// Array literal where everything is pure - promote the indexed item.
|
// Array literal where everything is pure - promote the indexed item.
|
||||||
// All other items can be thrown away.
|
// All other items can be thrown away.
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
let mut expr = a.remove(i as usize);
|
let mut result = a.remove(*i as usize);
|
||||||
expr.set_position(pos);
|
result.set_position(*pos);
|
||||||
expr
|
*expr = result;
|
||||||
}
|
}
|
||||||
// map[string]
|
// map[string]
|
||||||
(Expr::Map(m, pos), Expr::StringConstant(s)) if m.iter().all(|(_, x)| x.is_pure()) => {
|
(Expr::Map(m, pos), Expr::StringConstant(s, _)) if m.iter().all(|(_, x)| x.is_pure()) => {
|
||||||
// Map literal where everything is pure - promote the indexed item.
|
// Map literal where everything is pure - promote the indexed item.
|
||||||
// All other items can be thrown away.
|
// All other items can be thrown away.
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
m.into_iter().find(|(x, _)| x.name == s.name)
|
*expr = mem::take(m).into_iter().find(|(x, _)| x.name == *s)
|
||||||
.map(|(_, mut expr)| { expr.set_position(pos); expr })
|
.map(|(_, mut expr)| { expr.set_position(*pos); expr })
|
||||||
.unwrap_or_else(|| Expr::Unit(pos))
|
.unwrap_or_else(|| Expr::Unit(*pos));
|
||||||
}
|
}
|
||||||
// string[int]
|
// string[int]
|
||||||
(Expr::StringConstant(s), Expr::IntegerConstant(i, _)) if i >= 0 && (i as usize) < s.name.chars().count() => {
|
(Expr::StringConstant(s, pos), Expr::IntegerConstant(i, _)) if *i >= 0 && (*i as usize) < s.chars().count() => {
|
||||||
// String literal indexing - get the character
|
// String literal indexing - get the character
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
Expr::CharConstant(s.name.chars().nth(i as usize).unwrap(), s.pos)
|
*expr = Expr::CharConstant(s.chars().nth(*i as usize).unwrap(), *pos);
|
||||||
}
|
}
|
||||||
// var[rhs]
|
// var[rhs]
|
||||||
(lhs @ Expr::Variable(_), rhs) => Expr::Index(Box::new(BinaryExpr {
|
(Expr::Variable(_), rhs) => optimize_expr(rhs, state),
|
||||||
lhs,
|
|
||||||
rhs: optimize_expr(rhs, state),
|
|
||||||
}), idx_pos),
|
|
||||||
// lhs[rhs]
|
// lhs[rhs]
|
||||||
(lhs, rhs) => Expr::Index(Box::new(BinaryExpr {
|
(lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); }
|
||||||
lhs: optimize_expr(lhs, state),
|
|
||||||
rhs: optimize_expr(rhs, state),
|
|
||||||
}), idx_pos),
|
|
||||||
},
|
},
|
||||||
// [ items .. ]
|
// [ items .. ]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Array(a, pos) => Expr::Array(Box::new(a
|
Expr::Array(a, _) => a.iter_mut().for_each(|expr| optimize_expr(expr, state)),
|
||||||
.into_iter().map(|expr| optimize_expr(expr, state))
|
// #{ key:value, .. }
|
||||||
.collect()), pos),
|
|
||||||
// [ items .. ]
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Map(m, pos) => Expr::Map(Box::new(m
|
Expr::Map(m, _) => m.iter_mut().for_each(|(_, expr)| optimize_expr(expr, state)),
|
||||||
.into_iter().map(|(key, expr)| (key, optimize_expr(expr, state)))
|
|
||||||
.collect()), pos),
|
|
||||||
// lhs in rhs
|
// lhs in rhs
|
||||||
Expr::In(x, in_pos) => match (x.lhs, x.rhs) {
|
Expr::In(x, _) => match (&mut x.lhs, &mut x.rhs) {
|
||||||
// "xxx" in "xxxxx"
|
// "xxx" in "xxxxx"
|
||||||
(Expr::StringConstant(a), Expr::StringConstant(b)) => {
|
(Expr::StringConstant(a, pos), Expr::StringConstant(b, _)) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
if b.name.contains(a.name.as_str()) { Expr::True(a.pos) } else { Expr::False(a.pos) }
|
*expr = if b.contains(a.as_str()) { Expr::True(*pos) } else { Expr::False(*pos) };
|
||||||
}
|
}
|
||||||
// 'x' in "xxxxx"
|
// 'x' in "xxxxx"
|
||||||
(Expr::CharConstant(a, pos), Expr::StringConstant(b)) => {
|
(Expr::CharConstant(a, pos), Expr::StringConstant(b, _)) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
if b.name.contains(a) { Expr::True(pos) } else { Expr::False(pos) }
|
*expr = if b.contains(*a) { Expr::True(*pos) } else { Expr::False(*pos) };
|
||||||
}
|
}
|
||||||
// "xxx" in #{...}
|
// "xxx" in #{...}
|
||||||
(Expr::StringConstant(a), Expr::Map(b, _)) => {
|
(Expr::StringConstant(a, pos), Expr::Map(b, _)) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
if b.iter().find(|(x, _)| x.name == a.name).is_some() {
|
*expr = if b.iter().find(|(x, _)| x.name == *a).is_some() {
|
||||||
Expr::True(a.pos)
|
Expr::True(*pos)
|
||||||
} else {
|
} else {
|
||||||
Expr::False(a.pos)
|
Expr::False(*pos)
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
// 'x' in #{...}
|
// 'x' in #{...}
|
||||||
(Expr::CharConstant(a, pos), Expr::Map(b, _)) => {
|
(Expr::CharConstant(a, pos), Expr::Map(b, _)) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
let ch = a.to_string();
|
let ch = a.to_string();
|
||||||
|
|
||||||
if b.iter().find(|(x, _)| x.name == &ch).is_some() {
|
*expr = if b.iter().find(|(x, _)| x.name == &ch).is_some() {
|
||||||
Expr::True(pos)
|
Expr::True(*pos)
|
||||||
} else {
|
} else {
|
||||||
Expr::False(pos)
|
Expr::False(*pos)
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
// lhs in rhs
|
// lhs in rhs
|
||||||
(lhs, rhs) => Expr::In(Box::new(BinaryExpr {
|
(lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); }
|
||||||
lhs: optimize_expr(lhs, state),
|
|
||||||
rhs: optimize_expr(rhs, state),
|
|
||||||
}), in_pos),
|
|
||||||
},
|
},
|
||||||
// lhs && rhs
|
// lhs && rhs
|
||||||
Expr::And(x, and_pos) => match (x.lhs, x.rhs) {
|
Expr::And(x, _) => match (&mut x.lhs, &mut x.rhs) {
|
||||||
// true && rhs -> rhs
|
// true && rhs -> rhs
|
||||||
(Expr::True(_), rhs) => {
|
(Expr::True(_), rhs) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
rhs
|
optimize_expr(rhs, state);
|
||||||
|
*expr = mem::take(rhs);
|
||||||
}
|
}
|
||||||
// false && rhs -> false
|
// false && rhs -> false
|
||||||
(Expr::False(pos), _) => {
|
(Expr::False(pos), _) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
Expr::False(pos)
|
*expr = Expr::False(*pos);
|
||||||
}
|
}
|
||||||
// lhs && true -> lhs
|
// lhs && true -> lhs
|
||||||
(lhs, Expr::True(_)) => {
|
(lhs, Expr::True(_)) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
optimize_expr(lhs, state)
|
optimize_expr(lhs, state);
|
||||||
|
*expr = mem::take(lhs);
|
||||||
}
|
}
|
||||||
// lhs && rhs
|
// lhs && rhs
|
||||||
(lhs, rhs) => Expr::And(Box::new(BinaryExpr {
|
(lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); }
|
||||||
lhs: optimize_expr(lhs, state),
|
|
||||||
rhs: optimize_expr(rhs, state),
|
|
||||||
}), and_pos),
|
|
||||||
},
|
},
|
||||||
// lhs || rhs
|
// lhs || rhs
|
||||||
Expr::Or(x, or_pos) => match (x.lhs, x.rhs) {
|
Expr::Or(ref mut x, _) => match (&mut x.lhs, &mut x.rhs) {
|
||||||
// false || rhs -> rhs
|
// false || rhs -> rhs
|
||||||
(Expr::False(_), rhs) => {
|
(Expr::False(_), rhs) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
rhs
|
optimize_expr(rhs, state);
|
||||||
|
*expr = mem::take(rhs);
|
||||||
}
|
}
|
||||||
// true || rhs -> true
|
// true || rhs -> true
|
||||||
(Expr::True(pos), _) => {
|
(Expr::True(pos), _) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
Expr::True(pos)
|
*expr = Expr::True(*pos);
|
||||||
}
|
}
|
||||||
// lhs || false
|
// lhs || false
|
||||||
(lhs, Expr::False(_)) => {
|
(lhs, Expr::False(_)) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
optimize_expr(lhs, state)
|
optimize_expr(lhs, state);
|
||||||
|
*expr = mem::take(lhs);
|
||||||
}
|
}
|
||||||
// lhs || rhs
|
// lhs || rhs
|
||||||
(lhs, rhs) => Expr::Or(Box::new(BinaryExpr {
|
(lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); }
|
||||||
lhs: optimize_expr(lhs, state),
|
|
||||||
rhs: optimize_expr(rhs, state),
|
|
||||||
}), or_pos),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Do not call some special keywords
|
// Do not call some special keywords
|
||||||
Expr::FnCall(mut x, pos) if DONT_EVAL_KEYWORDS.contains(&x.name.as_ref()) => {
|
Expr::FnCall(x, _) if DONT_EVAL_KEYWORDS.contains(&x.name.as_ref()) => {
|
||||||
x.args = x.args.into_iter().map(|a| optimize_expr(a, state)).collect();
|
x.args.iter_mut().for_each(|a| optimize_expr(a, state));
|
||||||
Expr::FnCall(x, pos)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call built-in operators
|
// Call built-in operators
|
||||||
Expr::FnCall(mut x, pos)
|
Expr::FnCall(x, pos)
|
||||||
if x.namespace.is_none() // Non-qualified
|
if x.namespace.is_none() // Non-qualified
|
||||||
&& state.optimization_level == OptimizationLevel::Simple // simple optimizations
|
&& state.optimization_level == OptimizationLevel::Simple // simple optimizations
|
||||||
&& x.args.len() == 2 // binary call
|
&& x.args.len() == 2 // binary call
|
||||||
&& x.args.iter().all(Expr::is_constant) // all arguments are constants
|
&& x.args.iter().all(Expr::is_constant) // all arguments are constants
|
||||||
&& !is_valid_identifier(x.name.chars()) // cannot be scripted
|
&& !is_valid_identifier(x.name.chars()) // cannot be scripted
|
||||||
=> {
|
=> {
|
||||||
let FnCallInfo { name, args, .. } = x.as_mut();
|
let arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap()).collect();
|
||||||
|
|
||||||
let arg_values: StaticVec<_> = args.iter().map(|e| e.get_constant_value().unwrap()).collect();
|
|
||||||
let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect();
|
let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect();
|
||||||
|
|
||||||
// Search for overloaded operators (can override built-in).
|
// Search for overloaded operators (can override built-in).
|
||||||
if !state.engine.has_override_by_name_and_arguments(state.lib, name, arg_types.as_ref(), false) {
|
if !state.engine.has_override_by_name_and_arguments(state.lib, x.name.as_ref(), arg_types.as_ref(), false) {
|
||||||
if let Some(expr) = run_builtin_binary_op(name, &arg_values[0], &arg_values[1])
|
if let Some(result) = run_builtin_binary_op(x.name.as_ref(), &arg_values[0], &arg_values[1])
|
||||||
.ok().flatten()
|
.ok().flatten()
|
||||||
.and_then(|result| map_dynamic_to_expr(result, pos))
|
.and_then(|result| map_dynamic_to_expr(result, *pos))
|
||||||
{
|
{
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
return expr;
|
*expr = result;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
x.args = x.args.into_iter().map(|a| optimize_expr(a, state)).collect();
|
x.args.iter_mut().for_each(|a| optimize_expr(a, state));
|
||||||
Expr::FnCall(x, pos)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Eagerly call functions
|
// Eagerly call functions
|
||||||
Expr::FnCall(mut x, pos)
|
Expr::FnCall(x, pos)
|
||||||
if x.namespace.is_none() // Non-qualified
|
if x.namespace.is_none() // Non-qualified
|
||||||
&& state.optimization_level == OptimizationLevel::Full // full optimizations
|
&& state.optimization_level == OptimizationLevel::Full // full optimizations
|
||||||
&& x.args.iter().all(Expr::is_constant) // all arguments are constants
|
&& x.args.iter().all(Expr::is_constant) // all arguments are constants
|
||||||
=> {
|
=> {
|
||||||
let FnCallInfo { name, args, def_value, .. } = x.as_mut();
|
|
||||||
|
|
||||||
// First search for script-defined functions (can override built-in)
|
// First search for script-defined functions (can override built-in)
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
let has_script_fn = state.lib.iter().any(|&m| m.get_script_fn(name, args.len(), false).is_some());
|
let has_script_fn = state.lib.iter().any(|&m| m.get_script_fn(x.name.as_ref(), x.args.len(), false).is_some());
|
||||||
#[cfg(feature = "no_function")]
|
#[cfg(feature = "no_function")]
|
||||||
let has_script_fn = false;
|
let has_script_fn = false;
|
||||||
|
|
||||||
if !has_script_fn {
|
if !has_script_fn {
|
||||||
let mut arg_values: StaticVec<_> = args.iter().map(|e| e.get_constant_value().unwrap()).collect();
|
let mut arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap()).collect();
|
||||||
|
|
||||||
// Save the typename of the first argument if it is `type_of()`
|
// Save the typename of the first argument if it is `type_of()`
|
||||||
// This is to avoid `call_args` being passed into the closure
|
// This is to avoid `call_args` being passed into the closure
|
||||||
let arg_for_type_of = if name == KEYWORD_TYPE_OF && arg_values.len() == 1 {
|
let arg_for_type_of = if x.name == KEYWORD_TYPE_OF && arg_values.len() == 1 {
|
||||||
state.engine.map_type_name(arg_values[0].type_name())
|
state.engine.map_type_name(arg_values[0].type_name())
|
||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(expr) = call_fn_with_constant_arguments(&state, name, arg_values.as_mut())
|
if let Some(result) = call_fn_with_constant_arguments(&state, x.name.as_ref(), arg_values.as_mut())
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
if !arg_for_type_of.is_empty() {
|
if !arg_for_type_of.is_empty() {
|
||||||
// Handle `type_of()`
|
// Handle `type_of()`
|
||||||
Some(arg_for_type_of.to_string().into())
|
Some(arg_for_type_of.to_string().into())
|
||||||
} else {
|
} else {
|
||||||
// Otherwise use the default value, if any
|
// Otherwise use the default value, if any
|
||||||
def_value.map(|v| v.into())
|
x.def_value.map(|v| v.into())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.and_then(|result| map_dynamic_to_expr(result, pos))
|
.and_then(|result| map_dynamic_to_expr(result, *pos))
|
||||||
{
|
{
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
return expr;
|
*expr = result;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
x.args = x.args.into_iter().map(|a| optimize_expr(a, state)).collect();
|
x.args.iter_mut().for_each(|a| optimize_expr(a, state));
|
||||||
Expr::FnCall(x, pos)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// id(args ..) -> optimize function call arguments
|
// id(args ..) -> optimize function call arguments
|
||||||
Expr::FnCall(mut x, pos) => {
|
Expr::FnCall(x, _) => x.args.iter_mut().for_each(|a| optimize_expr(a, state)),
|
||||||
x.args = x.args.into_iter().map(|a| optimize_expr(a, state)).collect();
|
|
||||||
Expr::FnCall(x, pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
// constant-name
|
// constant-name
|
||||||
Expr::Variable(x) if x.1.is_none() && state.contains_constant(&x.3.name) => {
|
Expr::Variable(x) if x.1.is_none() && state.contains_constant(&x.3.name) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
|
|
||||||
// Replace constant with value
|
// Replace constant with value
|
||||||
let mut expr = state.find_constant(&x.3.name).unwrap().clone();
|
let mut result = state.find_constant(&x.3.name).unwrap().clone();
|
||||||
expr.set_position(x.3.pos);
|
result.set_position(x.3.pos);
|
||||||
expr
|
*expr = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// switch const { ... }
|
||||||
|
Expr::Switch(x, pos) if x.0.is_constant() => {
|
||||||
|
let value = x.0.get_constant_value().unwrap();
|
||||||
|
let hasher = &mut get_hasher();
|
||||||
|
value.hash(hasher);
|
||||||
|
let hash = hasher.finish();
|
||||||
|
|
||||||
|
state.set_dirty();
|
||||||
|
|
||||||
|
let table = &mut x.1;
|
||||||
|
|
||||||
|
if let Some(stmt) = table.get_mut(&hash) {
|
||||||
|
optimize_stmt(stmt, state, true);
|
||||||
|
*expr = Expr::Stmt(Box::new(vec![mem::take(stmt)].into()), *pos);
|
||||||
|
} else if let Some(def_stmt) = x.2.as_mut() {
|
||||||
|
optimize_stmt(def_stmt, state, true);
|
||||||
|
*expr = Expr::Stmt(Box::new(vec![mem::take(def_stmt)].into()), *pos);
|
||||||
|
} else {
|
||||||
|
*expr = Expr::Unit(*pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// switch
|
||||||
|
Expr::Switch(x, _) => {
|
||||||
|
optimize_expr(&mut x.0, state);
|
||||||
|
x.1.values_mut().for_each(|stmt| optimize_stmt(stmt, state, true));
|
||||||
|
if let Some(def_stmt) = x.2.as_mut() {
|
||||||
|
optimize_stmt(def_stmt, state, true);
|
||||||
|
|
||||||
|
match def_stmt {
|
||||||
|
Stmt::Noop(_) | Stmt::Expr(Expr::Unit(_)) => x.2 = None,
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom syntax
|
// Custom syntax
|
||||||
Expr::Custom(x, pos) => Expr::Custom(Box::new(CustomExpr {
|
Expr::Custom(x, _) => x.keywords.iter_mut().for_each(|expr| optimize_expr(expr, state)),
|
||||||
keywords: x.keywords.into_iter().map(|expr| optimize_expr(expr, state)).collect(),
|
|
||||||
..*x
|
|
||||||
}), pos),
|
|
||||||
|
|
||||||
// All other expressions - skip
|
// All other expressions - skip
|
||||||
expr => expr,
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -773,32 +777,25 @@ fn optimize(
|
|||||||
|
|
||||||
let num_statements = result.len();
|
let num_statements = result.len();
|
||||||
|
|
||||||
result = result
|
result.iter_mut().enumerate().for_each(|(i, stmt)| {
|
||||||
.into_iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, stmt)| {
|
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::Const(var_def, Some(expr), export, pos) => {
|
Stmt::Const(var_def, expr, _, _) if expr.is_some() => {
|
||||||
// Load constants
|
// Load constants
|
||||||
let expr = optimize_expr(expr, &mut state);
|
let value_expr = expr.as_mut().unwrap();
|
||||||
|
optimize_expr(value_expr, &mut state);
|
||||||
|
|
||||||
if expr.is_literal() {
|
if value_expr.is_constant() {
|
||||||
state.push_constant(&var_def.name, expr.clone());
|
state.push_constant(&var_def.name, value_expr.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep it in the global scope
|
// Keep it in the global scope
|
||||||
if expr.is_unit() {
|
if value_expr.is_unit() {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
Stmt::Const(var_def, None, export, pos)
|
*expr = None;
|
||||||
} else {
|
|
||||||
Stmt::Const(var_def, Some(expr), export, pos)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Stmt::Const(ref var_def, None, _, _) => {
|
Stmt::Const(var_def, None, _, _) => {
|
||||||
state.push_constant(&var_def.name, Expr::Unit(var_def.pos));
|
state.push_constant(&var_def.name, Expr::Unit(var_def.pos));
|
||||||
|
|
||||||
// Keep it in the global scope
|
|
||||||
stmt
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Keep all variable declarations at this level
|
// Keep all variable declarations at this level
|
||||||
@ -809,11 +806,10 @@ fn optimize(
|
|||||||
Stmt::Import(_, _, _) => true,
|
Stmt::Import(_, _, _) => true,
|
||||||
_ => i == num_statements - 1,
|
_ => i == num_statements - 1,
|
||||||
};
|
};
|
||||||
optimize_stmt(stmt, &mut state, keep)
|
optimize_stmt(stmt, &mut state, keep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
.collect();
|
|
||||||
|
|
||||||
if !state.is_dirty() {
|
if !state.is_dirty() {
|
||||||
break;
|
break;
|
||||||
|
@ -281,7 +281,7 @@ mod array_functions {
|
|||||||
})
|
})
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
Box::new(EvalAltResult::ErrorInFunctionCall(
|
Box::new(EvalAltResult::ErrorInFunctionCall(
|
||||||
"filter".to_string(),
|
"index_of".to_string(),
|
||||||
err,
|
err,
|
||||||
NO_POS,
|
NO_POS,
|
||||||
))
|
))
|
||||||
@ -314,7 +314,7 @@ mod array_functions {
|
|||||||
})
|
})
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
Box::new(EvalAltResult::ErrorInFunctionCall(
|
Box::new(EvalAltResult::ErrorInFunctionCall(
|
||||||
"filter".to_string(),
|
"some".to_string(),
|
||||||
err,
|
err,
|
||||||
NO_POS,
|
NO_POS,
|
||||||
))
|
))
|
||||||
@ -347,7 +347,7 @@ mod array_functions {
|
|||||||
})
|
})
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
Box::new(EvalAltResult::ErrorInFunctionCall(
|
Box::new(EvalAltResult::ErrorInFunctionCall(
|
||||||
"filter".to_string(),
|
"all".to_string(),
|
||||||
err,
|
err,
|
||||||
NO_POS,
|
NO_POS,
|
||||||
))
|
))
|
||||||
@ -449,7 +449,7 @@ mod array_functions {
|
|||||||
})
|
})
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
Box::new(EvalAltResult::ErrorInFunctionCall(
|
Box::new(EvalAltResult::ErrorInFunctionCall(
|
||||||
"reduce".to_string(),
|
"reduce_rev".to_string(),
|
||||||
err,
|
err,
|
||||||
NO_POS,
|
NO_POS,
|
||||||
))
|
))
|
||||||
@ -467,7 +467,7 @@ mod array_functions {
|
|||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
let mut result = initial.call_dynamic(ctx, None, []).map_err(|err| {
|
let mut result = initial.call_dynamic(ctx, None, []).map_err(|err| {
|
||||||
Box::new(EvalAltResult::ErrorInFunctionCall(
|
Box::new(EvalAltResult::ErrorInFunctionCall(
|
||||||
"reduce".to_string(),
|
"reduce_rev".to_string(),
|
||||||
err,
|
err,
|
||||||
NO_POS,
|
NO_POS,
|
||||||
))
|
))
|
||||||
@ -486,7 +486,7 @@ mod array_functions {
|
|||||||
})
|
})
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
Box::new(EvalAltResult::ErrorInFunctionCall(
|
Box::new(EvalAltResult::ErrorInFunctionCall(
|
||||||
"reduce".to_string(),
|
"reduce_rev".to_string(),
|
||||||
err,
|
err,
|
||||||
NO_POS,
|
NO_POS,
|
||||||
))
|
))
|
||||||
@ -556,7 +556,7 @@ mod array_functions {
|
|||||||
})
|
})
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
Box::new(EvalAltResult::ErrorInFunctionCall(
|
Box::new(EvalAltResult::ErrorInFunctionCall(
|
||||||
"filter".to_string(),
|
"drain".to_string(),
|
||||||
err,
|
err,
|
||||||
NO_POS,
|
NO_POS,
|
||||||
))
|
))
|
||||||
@ -615,7 +615,7 @@ mod array_functions {
|
|||||||
})
|
})
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
Box::new(EvalAltResult::ErrorInFunctionCall(
|
Box::new(EvalAltResult::ErrorInFunctionCall(
|
||||||
"filter".to_string(),
|
"retain".to_string(),
|
||||||
err,
|
err,
|
||||||
NO_POS,
|
NO_POS,
|
||||||
))
|
))
|
||||||
|
@ -108,6 +108,8 @@ pub enum ParseErrorType {
|
|||||||
///
|
///
|
||||||
/// Never appears under the `no_object` feature.
|
/// Never appears under the `no_object` feature.
|
||||||
DuplicatedProperty(String),
|
DuplicatedProperty(String),
|
||||||
|
/// A switch case is duplicated.
|
||||||
|
DuplicatedSwitchCase,
|
||||||
/// Missing a property name for custom types and maps.
|
/// Missing a property name for custom types and maps.
|
||||||
///
|
///
|
||||||
/// Never appears under the `no_object` feature.
|
/// Never appears under the `no_object` feature.
|
||||||
@ -177,6 +179,7 @@ impl ParseErrorType {
|
|||||||
Self::MalformedInExpr(_) => "Invalid 'in' expression",
|
Self::MalformedInExpr(_) => "Invalid 'in' expression",
|
||||||
Self::MalformedCapture(_) => "Invalid capturing",
|
Self::MalformedCapture(_) => "Invalid capturing",
|
||||||
Self::DuplicatedProperty(_) => "Duplicated property in object map literal",
|
Self::DuplicatedProperty(_) => "Duplicated property in object map literal",
|
||||||
|
Self::DuplicatedSwitchCase => "Duplicated switch case",
|
||||||
Self::PropertyExpected => "Expecting name of a property",
|
Self::PropertyExpected => "Expecting name of a property",
|
||||||
Self::VariableExpected => "Expecting name of a variable",
|
Self::VariableExpected => "Expecting name of a variable",
|
||||||
Self::Reserved(_) => "Invalid use of reserved keyword",
|
Self::Reserved(_) => "Invalid use of reserved keyword",
|
||||||
@ -211,6 +214,7 @@ impl fmt::Display for ParseErrorType {
|
|||||||
Self::DuplicatedProperty(s) => {
|
Self::DuplicatedProperty(s) => {
|
||||||
write!(f, "Duplicated property '{}' for object map literal", s)
|
write!(f, "Duplicated property '{}' for object map literal", s)
|
||||||
}
|
}
|
||||||
|
Self::DuplicatedSwitchCase => f.write_str(self.desc()),
|
||||||
|
|
||||||
Self::ExprExpected(s) => write!(f, "Expecting {} expression", s),
|
Self::ExprExpected(s) => write!(f, "Expecting {} expression", s),
|
||||||
|
|
||||||
|
495
src/parser.rs
495
src/parser.rs
File diff suppressed because it is too large
Load Diff
@ -34,7 +34,7 @@ pub type FnCustomSyntaxParse =
|
|||||||
dyn Fn(&[String]) -> Result<Option<String>, ParseError> + Send + Sync;
|
dyn Fn(&[String]) -> Result<Option<String>, ParseError> + Send + Sync;
|
||||||
|
|
||||||
/// An expression sub-tree in an AST.
|
/// An expression sub-tree in an AST.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Expression<'a>(&'a Expr);
|
pub struct Expression<'a>(&'a Expr);
|
||||||
|
|
||||||
impl<'a> From<&'a Expr> for Expression<'a> {
|
impl<'a> From<&'a Expr> for Expression<'a> {
|
||||||
|
41
src/token.rs
41
src/token.rs
@ -216,6 +216,10 @@ pub enum Token {
|
|||||||
Colon,
|
Colon,
|
||||||
/// `::`
|
/// `::`
|
||||||
DoubleColon,
|
DoubleColon,
|
||||||
|
/// `=>`
|
||||||
|
DoubleArrow,
|
||||||
|
/// `_`
|
||||||
|
Underscore,
|
||||||
/// `,`
|
/// `,`
|
||||||
Comma,
|
Comma,
|
||||||
/// `.`
|
/// `.`
|
||||||
@ -236,6 +240,8 @@ pub enum Token {
|
|||||||
If,
|
If,
|
||||||
/// `else`
|
/// `else`
|
||||||
Else,
|
Else,
|
||||||
|
/// `switch`
|
||||||
|
Switch,
|
||||||
/// `while`
|
/// `while`
|
||||||
While,
|
While,
|
||||||
/// `loop`
|
/// `loop`
|
||||||
@ -371,6 +377,8 @@ impl Token {
|
|||||||
SemiColon => ";",
|
SemiColon => ";",
|
||||||
Colon => ":",
|
Colon => ":",
|
||||||
DoubleColon => "::",
|
DoubleColon => "::",
|
||||||
|
DoubleArrow => "=>",
|
||||||
|
Underscore => "_",
|
||||||
Comma => ",",
|
Comma => ",",
|
||||||
Period => ".",
|
Period => ".",
|
||||||
MapStart => "#{",
|
MapStart => "#{",
|
||||||
@ -381,6 +389,7 @@ impl Token {
|
|||||||
Const => "const",
|
Const => "const",
|
||||||
If => "if",
|
If => "if",
|
||||||
Else => "else",
|
Else => "else",
|
||||||
|
Switch => "switch",
|
||||||
While => "while",
|
While => "while",
|
||||||
Loop => "loop",
|
Loop => "loop",
|
||||||
For => "for",
|
For => "for",
|
||||||
@ -455,6 +464,8 @@ impl Token {
|
|||||||
";" => SemiColon,
|
";" => SemiColon,
|
||||||
":" => Colon,
|
":" => Colon,
|
||||||
"::" => DoubleColon,
|
"::" => DoubleColon,
|
||||||
|
"=>" => DoubleArrow,
|
||||||
|
"_" => Underscore,
|
||||||
"," => Comma,
|
"," => Comma,
|
||||||
"." => Period,
|
"." => Period,
|
||||||
"#{" => MapStart,
|
"#{" => MapStart,
|
||||||
@ -465,6 +476,7 @@ impl Token {
|
|||||||
"const" => Const,
|
"const" => Const,
|
||||||
"if" => If,
|
"if" => If,
|
||||||
"else" => Else,
|
"else" => Else,
|
||||||
|
"switch" => Switch,
|
||||||
"while" => While,
|
"while" => While,
|
||||||
"loop" => Loop,
|
"loop" => Loop,
|
||||||
"for" => For,
|
"for" => For,
|
||||||
@ -521,11 +533,12 @@ impl Token {
|
|||||||
#[cfg(feature = "no_module")]
|
#[cfg(feature = "no_module")]
|
||||||
"import" | "export" | "as" => Reserved(syntax.into()),
|
"import" | "export" | "as" => Reserved(syntax.into()),
|
||||||
|
|
||||||
"===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "public"
|
"===" | "!==" | "->" | "<-" | ":=" | "::<" | "(*" | "*)" | "#" | "public" | "new"
|
||||||
| "new" | "use" | "module" | "package" | "var" | "static" | "shared" | "with"
|
| "use" | "module" | "package" | "var" | "static" | "shared" | "with" | "do"
|
||||||
| "do" | "each" | "then" | "goto" | "exit" | "switch" | "match" | "case"
|
| "each" | "then" | "goto" | "exit" | "match" | "case" | "default" | "void"
|
||||||
| "default" | "void" | "null" | "nil" | "spawn" | "thread" | "go" | "sync"
|
| "null" | "nil" | "spawn" | "thread" | "go" | "sync" | "async" | "await" | "yield" => {
|
||||||
| "async" | "await" | "yield" => Reserved(syntax.into()),
|
Reserved(syntax.into())
|
||||||
|
}
|
||||||
|
|
||||||
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
||||||
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_DEF_VAR
|
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_DEF_VAR
|
||||||
@ -1318,7 +1331,7 @@ fn get_next_token_inner(
|
|||||||
}
|
}
|
||||||
('=', '>') => {
|
('=', '>') => {
|
||||||
eat_next(stream, pos);
|
eat_next(stream, pos);
|
||||||
return Some((Token::Reserved("=>".into()), start_pos));
|
return Some((Token::DoubleArrow, start_pos));
|
||||||
}
|
}
|
||||||
('=', _) => return Some((Token::Equals, start_pos)),
|
('=', _) => return Some((Token::Equals, start_pos)),
|
||||||
|
|
||||||
@ -1481,7 +1494,11 @@ fn get_identifier(
|
|||||||
|
|
||||||
let is_valid_identifier = is_valid_identifier(result.iter().cloned());
|
let is_valid_identifier = is_valid_identifier(result.iter().cloned());
|
||||||
|
|
||||||
let identifier = result.into_iter().collect();
|
let identifier: String = result.into_iter().collect();
|
||||||
|
|
||||||
|
if let Some(token) = Token::lookup_from_syntax(&identifier) {
|
||||||
|
return Some((token, start_pos));
|
||||||
|
}
|
||||||
|
|
||||||
if !is_valid_identifier {
|
if !is_valid_identifier {
|
||||||
return Some((
|
return Some((
|
||||||
@ -1490,10 +1507,7 @@ fn get_identifier(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Some((
|
return Some((Token::Identifier(identifier), start_pos));
|
||||||
Token::lookup_from_syntax(&identifier).unwrap_or_else(|| Token::Identifier(identifier)),
|
|
||||||
start_pos,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is this keyword allowed as a function?
|
/// Is this keyword allowed as a function?
|
||||||
@ -1654,9 +1668,6 @@ impl<'a> Iterator for TokenIterator<'a, '_> {
|
|||||||
("<-", false) => Token::LexError(LERR::ImproperSymbol(
|
("<-", false) => Token::LexError(LERR::ImproperSymbol(
|
||||||
"'<-' is not a valid symbol. This is not Go! Should it be '<='?".to_string(),
|
"'<-' is not a valid symbol. This is not Go! Should it be '<='?".to_string(),
|
||||||
)),
|
)),
|
||||||
("=>", false) => Token::LexError(LERR::ImproperSymbol(
|
|
||||||
"'=>' is not a valid symbol. This is not Rust! Should it be '>='?".to_string(),
|
|
||||||
)),
|
|
||||||
(":=", false) => Token::LexError(LERR::ImproperSymbol(
|
(":=", false) => Token::LexError(LERR::ImproperSymbol(
|
||||||
"':=' is not a valid assignment operator. This is not Go! Should it be simply '='?".to_string(),
|
"':=' is not a valid assignment operator. This is not Go! Should it be simply '='?".to_string(),
|
||||||
)),
|
)),
|
||||||
@ -1735,7 +1746,7 @@ impl Engine {
|
|||||||
engine: self,
|
engine: self,
|
||||||
state: TokenizeState {
|
state: TokenizeState {
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
max_string_size: self.limits_set.max_string_size,
|
max_string_size: self.limits.max_string_size,
|
||||||
#[cfg(feature = "unchecked")]
|
#[cfg(feature = "unchecked")]
|
||||||
max_string_size: 0,
|
max_string_size: 0,
|
||||||
non_unary: false,
|
non_unary: false,
|
||||||
|
23
src/utils.rs
23
src/utils.rs
@ -55,7 +55,7 @@ impl BuildHasher for StraightHasherBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// _[INTERNALS]_ Calculate a `u64` hash key from a module-qualified function name and parameter types.
|
/// _[INTERNALS]_ Calculate a `u64` hash key from a namespace-qualified function name and parameter types.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
///
|
///
|
||||||
/// Module names are passed in via `&str` references from an iterator.
|
/// Module names are passed in via `&str` references from an iterator.
|
||||||
@ -73,8 +73,8 @@ pub fn calc_native_fn_hash<'a>(
|
|||||||
calc_fn_hash(modules, fn_name, None, params)
|
calc_fn_hash(modules, fn_name, None, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// _[INTERNALS]_ Calculate a `u64` hash key from a module-qualified function name and the number of parameters,
|
/// _[INTERNALS]_ Calculate a `u64` hash key from a namespace-qualified function name
|
||||||
/// but no parameter types.
|
/// and the number of parameters, but no parameter types.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
///
|
///
|
||||||
/// Module names are passed in via `&str` references from an iterator.
|
/// Module names are passed in via `&str` references from an iterator.
|
||||||
@ -92,7 +92,17 @@ pub fn calc_script_fn_hash<'a>(
|
|||||||
calc_fn_hash(modules, fn_name, Some(num), empty())
|
calc_fn_hash(modules, fn_name, Some(num), empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate a `u64` hash key from a module-qualified function name and parameter types.
|
/// Create an instance of the default hasher.
|
||||||
|
pub fn get_hasher() -> impl Hasher {
|
||||||
|
#[cfg(feature = "no_std")]
|
||||||
|
let s: AHasher = Default::default();
|
||||||
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
let s = DefaultHasher::new();
|
||||||
|
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate a `u64` hash key from a namespace-qualified function name and parameter types.
|
||||||
///
|
///
|
||||||
/// Module names are passed in via `&str` references from an iterator.
|
/// Module names are passed in via `&str` references from an iterator.
|
||||||
/// Parameter types are passed in via `TypeId` values from an iterator.
|
/// Parameter types are passed in via `TypeId` values from an iterator.
|
||||||
@ -106,10 +116,7 @@ fn calc_fn_hash<'a>(
|
|||||||
num: Option<usize>,
|
num: Option<usize>,
|
||||||
params: impl Iterator<Item = TypeId>,
|
params: impl Iterator<Item = TypeId>,
|
||||||
) -> u64 {
|
) -> u64 {
|
||||||
#[cfg(feature = "no_std")]
|
let s = &mut get_hasher();
|
||||||
let s: &mut AHasher = &mut Default::default();
|
|
||||||
#[cfg(not(feature = "no_std"))]
|
|
||||||
let s = &mut DefaultHasher::new();
|
|
||||||
|
|
||||||
// We always skip the first module
|
// We always skip the first module
|
||||||
modules.skip(1).for_each(|m| m.hash(s));
|
modules.skip(1).for_each(|m| m.hash(s));
|
||||||
|
@ -40,7 +40,6 @@ fn test_bool_op_short_circuit() -> Result<(), Box<EvalAltResult>> {
|
|||||||
engine.eval::<bool>(
|
engine.eval::<bool>(
|
||||||
r"
|
r"
|
||||||
let x = true;
|
let x = true;
|
||||||
|
|
||||||
x || { throw; };
|
x || { throw; };
|
||||||
"
|
"
|
||||||
)?,
|
)?,
|
||||||
@ -51,7 +50,6 @@ fn test_bool_op_short_circuit() -> Result<(), Box<EvalAltResult>> {
|
|||||||
engine.eval::<bool>(
|
engine.eval::<bool>(
|
||||||
r"
|
r"
|
||||||
let x = false;
|
let x = false;
|
||||||
|
|
||||||
x && { throw; };
|
x && { throw; };
|
||||||
"
|
"
|
||||||
)?,
|
)?,
|
||||||
@ -69,7 +67,6 @@ fn test_bool_op_no_short_circuit1() {
|
|||||||
.eval::<bool>(
|
.eval::<bool>(
|
||||||
r"
|
r"
|
||||||
let x = true;
|
let x = true;
|
||||||
|
|
||||||
x | { throw; }
|
x | { throw; }
|
||||||
"
|
"
|
||||||
)
|
)
|
||||||
@ -84,7 +81,6 @@ fn test_bool_op_no_short_circuit2() {
|
|||||||
.eval::<bool>(
|
.eval::<bool>(
|
||||||
r"
|
r"
|
||||||
let x = false;
|
let x = false;
|
||||||
|
|
||||||
x & { throw; }
|
x & { throw; }
|
||||||
"
|
"
|
||||||
)
|
)
|
||||||
|
@ -73,7 +73,8 @@ fn test_fn_ptr() -> Result<(), Box<EvalAltResult>> {
|
|||||||
"#
|
"#
|
||||||
)
|
)
|
||||||
.expect_err("should error"),
|
.expect_err("should error"),
|
||||||
EvalAltResult::ErrorInFunctionCall(fn_name, err, _) if fn_name == "foo" && matches!(*err, EvalAltResult::ErrorUnboundThis(_))
|
EvalAltResult::ErrorInFunctionCall(fn_name, err, _)
|
||||||
|
if fn_name == "foo" && matches!(*err, EvalAltResult::ErrorUnboundThis(_))
|
||||||
));
|
));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
64
tests/switch.rs
Normal file
64
tests/switch.rs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
use rhai::{Engine, EvalAltResult, Scope, INT};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_switch() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let engine = Engine::new();
|
||||||
|
let mut scope = Scope::new();
|
||||||
|
scope.push("x", 42 as INT);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval_with_scope::<bool>(&mut scope, "switch x { 1 => (), 2 => 'a', 42 => true }")?,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval_with_scope::<bool>(&mut scope, "switch x { 1 => (), 2 => 'a', _ => true }")?,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval_with_scope::<()>(&mut scope, "switch x { 1 => 123, 2 => 'a' }")?,
|
||||||
|
()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval_with_scope::<INT>(
|
||||||
|
&mut scope,
|
||||||
|
"switch x { 1 => 123, 42 => { x / 2 }, _ => 999 }"
|
||||||
|
)?,
|
||||||
|
21
|
||||||
|
);
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval_with_scope::<INT>(
|
||||||
|
&mut scope,
|
||||||
|
r"
|
||||||
|
let y = [1, 2, 3];
|
||||||
|
|
||||||
|
switch y {
|
||||||
|
42 => 1,
|
||||||
|
true => 2,
|
||||||
|
[1, 2, 3] => 3,
|
||||||
|
_ => 9
|
||||||
|
}
|
||||||
|
"
|
||||||
|
)?,
|
||||||
|
3
|
||||||
|
);
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval_with_scope::<INT>(
|
||||||
|
&mut scope,
|
||||||
|
r"
|
||||||
|
let y = #{a:1, b:true, c:'x'};
|
||||||
|
|
||||||
|
switch y {
|
||||||
|
42 => 1,
|
||||||
|
true => 2,
|
||||||
|
#{b:true, c:'x', a:1} => 3,
|
||||||
|
_ => 9
|
||||||
|
}
|
||||||
|
"
|
||||||
|
)?,
|
||||||
|
3
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user