Add switch expression.
This commit is contained in:
parent
7d1b971b39
commit
55b4907f19
11
RELEASES.md
11
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
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
@ -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_ |
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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.
|
130
src/ast.rs
130
src/ast.rs
@ -5,7 +5,7 @@ use crate::fn_native::{FnPtr, Shared};
|
|||||||
use crate::module::{Module, NamespaceRef};
|
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;
|
||||||
|
|
||||||
@ -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;
|
||||||
|
|
||||||
@ -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),
|
||||||
@ -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,51 +810,13 @@ impl CustomExpr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// _[INTERNALS]_ A type wrapping a floating-point number.
|
|
||||||
/// Exported under the `internals` feature only.
|
|
||||||
///
|
|
||||||
/// This type is mainly used to provide a standard `Hash` implementation
|
|
||||||
/// for floating-point numbers, allowing `Expr` to derive `Hash`.
|
|
||||||
///
|
|
||||||
/// ## 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) {
|
|
||||||
TypeId::of::<FLOAT>().hash(state);
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// _[INTERNALS]_ A binary expression.
|
/// _[INTERNALS]_ A binary expression.
|
||||||
/// 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,
|
||||||
@ -877,7 +830,7 @@ 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 FnCallExpr {
|
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,
|
||||||
@ -905,19 +858,29 @@ pub struct FnCallExpr {
|
|||||||
/// ## 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(ImmutableString, Position),
|
StringConstant(ImmutableString, Position),
|
||||||
/// FnPtr constant.
|
/// FnPtr constant.
|
||||||
FnPointer(ImmutableString, Position),
|
FnPointer(ImmutableString, Position),
|
||||||
|
/// [ expr, ... ]
|
||||||
|
Array(Box<StaticVec<Expr>>, Position),
|
||||||
|
/// #{ name:expr, ... }
|
||||||
|
Map(Box<StaticVec<(IdentX, Expr)>>, Position),
|
||||||
|
/// true
|
||||||
|
True(Position),
|
||||||
|
/// false
|
||||||
|
False(Position),
|
||||||
|
/// ()
|
||||||
|
Unit(Position),
|
||||||
/// Variable access - (optional index, optional modules, hash, variable name)
|
/// Variable access - (optional index, optional modules, hash, variable name)
|
||||||
Variable(Box<(Option<NonZeroUsize>, Option<Box<NamespaceRef>>, u64, IdentX)>),
|
Variable(Box<(Option<NonZeroUsize>, Option<Box<NamespaceRef>>, u64, IdentX)>),
|
||||||
/// Property access - (getter, setter), prop
|
/// Property access - (getter, setter), prop
|
||||||
@ -932,22 +895,21 @@ pub enum Expr {
|
|||||||
Dot(Box<BinaryExpr>, Position),
|
Dot(Box<BinaryExpr>, Position),
|
||||||
/// expr[expr]
|
/// expr[expr]
|
||||||
Index(Box<BinaryExpr>, Position),
|
Index(Box<BinaryExpr>, Position),
|
||||||
/// [ expr, ... ]
|
/// switch expr { literal or _ => stmt, ... }
|
||||||
Array(Box<StaticVec<Expr>>, Position),
|
Switch(
|
||||||
/// #{ name:expr, ... }
|
Box<(
|
||||||
Map(Box<StaticVec<(IdentX, Expr)>>, Position),
|
Expr,
|
||||||
|
HashMap<u64, Stmt, StraightHasherBuilder>,
|
||||||
|
Option<Stmt>,
|
||||||
|
)>,
|
||||||
|
Position,
|
||||||
|
),
|
||||||
/// lhs in rhs
|
/// lhs in rhs
|
||||||
In(Box<BinaryExpr>, Position),
|
In(Box<BinaryExpr>, Position),
|
||||||
/// lhs && rhs
|
/// lhs && rhs
|
||||||
And(Box<BinaryExpr>, Position),
|
And(Box<BinaryExpr>, Position),
|
||||||
/// lhs || rhs
|
/// lhs || rhs
|
||||||
Or(Box<BinaryExpr>, Position),
|
Or(Box<BinaryExpr>, Position),
|
||||||
/// true
|
|
||||||
True(Position),
|
|
||||||
/// false
|
|
||||||
False(Position),
|
|
||||||
/// ()
|
|
||||||
Unit(Position),
|
|
||||||
/// Custom syntax
|
/// Custom syntax
|
||||||
Custom(Box<CustomExpr>, Position),
|
Custom(Box<CustomExpr>, Position),
|
||||||
}
|
}
|
||||||
@ -997,7 +959,7 @@ 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.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(
|
||||||
@ -1052,6 +1014,7 @@ impl Expr {
|
|||||||
Self::Stmt(_, pos) => *pos,
|
Self::Stmt(_, pos) => *pos,
|
||||||
Self::Variable(x) => (x.3).pos,
|
Self::Variable(x) => (x.3).pos,
|
||||||
Self::FnCall(_, pos) => *pos,
|
Self::FnCall(_, pos) => *pos,
|
||||||
|
Self::Switch(_, pos) => *pos,
|
||||||
|
|
||||||
Self::And(x, _) | Self::Or(x, _) | Self::In(x, _) => x.lhs.position(),
|
Self::And(x, _) | Self::Or(x, _) | Self::In(x, _) => x.lhs.position(),
|
||||||
|
|
||||||
@ -1083,6 +1046,7 @@ impl Expr {
|
|||||||
Self::Property(x) => (x.1).pos = new_pos,
|
Self::Property(x) => (x.1).pos = new_pos,
|
||||||
Self::Stmt(_, pos) => *pos = new_pos,
|
Self::Stmt(_, pos) => *pos = new_pos,
|
||||||
Self::FnCall(_, pos) => *pos = new_pos,
|
Self::FnCall(_, pos) => *pos = new_pos,
|
||||||
|
Self::Switch(_, pos) => *pos = new_pos,
|
||||||
Self::And(_, pos) | Self::Or(_, pos) | Self::In(_, pos) => *pos = new_pos,
|
Self::And(_, pos) | Self::Or(_, pos) | Self::In(_, pos) => *pos = new_pos,
|
||||||
Self::True(pos) | Self::False(pos) | Self::Unit(pos) => *pos = new_pos,
|
Self::True(pos) | Self::False(pos) | Self::Unit(pos) => *pos = new_pos,
|
||||||
Self::Dot(_, pos) | Self::Index(_, pos) => *pos = new_pos,
|
Self::Dot(_, pos) | Self::Index(_, pos) => *pos = new_pos,
|
||||||
@ -1124,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 {
|
||||||
@ -1211,6 +1142,7 @@ impl Expr {
|
|||||||
Self::StringConstant(_, _)
|
Self::StringConstant(_, _)
|
||||||
| Self::Stmt(_, _)
|
| Self::Stmt(_, _)
|
||||||
| Self::FnCall(_, _)
|
| Self::FnCall(_, _)
|
||||||
|
| Self::Switch(_, _)
|
||||||
| Self::Dot(_, _)
|
| Self::Dot(_, _)
|
||||||
| Self::Index(_, _)
|
| Self::Index(_, _)
|
||||||
| Self::Array(_, _)
|
| Self::Array(_, _)
|
||||||
|
@ -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};
|
||||||
@ -378,10 +378,15 @@ impl Hash for Dynamic {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Union::Array(a) => a.hash(state),
|
Union::Array(a) => a.hash(state),
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Union::Map(m) => m.iter().for_each(|(key, item)| {
|
Union::Map(m) => {
|
||||||
key.hash(state);
|
let mut buf: StaticVec<_> = m.keys().collect();
|
||||||
item.hash(state);
|
buf.sort();
|
||||||
}),
|
|
||||||
|
buf.into_iter().for_each(|key| {
|
||||||
|
key.hash(state);
|
||||||
|
m[key].hash(state);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
|
@ -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,
|
||||||
@ -1543,7 +1545,7 @@ 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.clone().into()),
|
Expr::StringConstant(x, _) => Ok(x.clone().into()),
|
||||||
Expr::CharConstant(x, _) => Ok((*x).into()),
|
Expr::CharConstant(x, _) => Ok((*x).into()),
|
||||||
Expr::FnPointer(x, _) => Ok(FnPtr::new_unchecked(x.clone(), Default::default()).into()),
|
Expr::FnPointer(x, _) => Ok(FnPtr::new_unchecked(x.clone(), Default::default()).into()),
|
||||||
@ -1666,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()
|
||||||
|
@ -27,7 +27,7 @@ use crate::{
|
|||||||
use crate::fn_register::{RegisterFn, RegisterResultFn};
|
use crate::fn_register::{RegisterFn, RegisterResultFn};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
use crate::{fn_args::FuncArgs, fn_call::ensure_no_data_race, module::Module, StaticVec};
|
use crate::{fn_args::FuncArgs, fn_call::ensure_no_data_race, module::Module, StaticVec, utils::get_hasher};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
use crate::optimize::optimize_into_ast;
|
use crate::optimize::optimize_into_ast;
|
||||||
@ -35,6 +35,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 +46,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 +915,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 +1070,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 +1083,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,10 +1169,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.
|
||||||
@ -1315,10 +1330,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)
|
||||||
}
|
}
|
||||||
@ -1445,8 +1462,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ 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, Position, 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,6 +19,7 @@ use crate::ast::ReturnType;
|
|||||||
|
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
|
hash::{Hash, Hasher},
|
||||||
iter::empty,
|
iter::empty,
|
||||||
mem,
|
mem,
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
@ -169,7 +171,7 @@ fn optimize_stmt_block(
|
|||||||
// Optimize each statement in the block
|
// Optimize each statement in the block
|
||||||
statements.iter_mut().for_each(|stmt| match stmt {
|
statements.iter_mut().for_each(|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, mem::take(expr));
|
state.push_constant(&var_def.name, mem::take(expr));
|
||||||
*stmt = Stmt::Noop(*pos); // No need to keep constants
|
*stmt = Stmt::Noop(*pos); // No need to keep constants
|
||||||
@ -695,6 +697,42 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
|||||||
*expr = result;
|
*expr = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// switch const { ... }
|
||||||
|
Expr::Switch(x, pos) if x.0.is_constant() => {
|
||||||
|
let value = x.0.get_constant_value().unwrap();
|
||||||
|
let hasher = &mut get_hasher();
|
||||||
|
value.hash(hasher);
|
||||||
|
let hash = hasher.finish();
|
||||||
|
|
||||||
|
state.set_dirty();
|
||||||
|
|
||||||
|
let table = &mut x.1;
|
||||||
|
|
||||||
|
if let Some(stmt) = table.get_mut(&hash) {
|
||||||
|
optimize_stmt(stmt, state, true);
|
||||||
|
*expr = Expr::Stmt(Box::new(vec![mem::take(stmt)].into()), *pos);
|
||||||
|
} else if let Some(def_stmt) = x.2.as_mut() {
|
||||||
|
optimize_stmt(def_stmt, state, true);
|
||||||
|
*expr = Expr::Stmt(Box::new(vec![mem::take(def_stmt)].into()), *pos);
|
||||||
|
} else {
|
||||||
|
*expr = Expr::Unit(*pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// switch
|
||||||
|
Expr::Switch(x, _) => {
|
||||||
|
optimize_expr(&mut x.0, state);
|
||||||
|
x.1.values_mut().for_each(|stmt| optimize_stmt(stmt, state, true));
|
||||||
|
if let Some(def_stmt) = x.2.as_mut() {
|
||||||
|
optimize_stmt(def_stmt, state, true);
|
||||||
|
|
||||||
|
match def_stmt {
|
||||||
|
Stmt::Noop(_) | Stmt::Expr(Expr::Unit(_)) => x.2 = None,
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Custom syntax
|
// Custom syntax
|
||||||
Expr::Custom(x, _) => x.keywords.iter_mut().for_each(|expr| optimize_expr(expr, state)),
|
Expr::Custom(x, _) => x.keywords.iter_mut().for_each(|expr| optimize_expr(expr, state)),
|
||||||
|
|
||||||
@ -746,7 +784,7 @@ fn optimize(
|
|||||||
let value_expr = expr.as_mut().unwrap();
|
let value_expr = expr.as_mut().unwrap();
|
||||||
optimize_expr(value_expr, &mut state);
|
optimize_expr(value_expr, &mut state);
|
||||||
|
|
||||||
if value_expr.is_literal() {
|
if value_expr.is_constant() {
|
||||||
state.push_constant(&var_def.name, value_expr.clone());
|
state.push_constant(&var_def.name, value_expr.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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),
|
||||||
|
|
||||||
|
263
src/parser.rs
263
src/parser.rs
@ -13,11 +13,11 @@ use crate::syntax::CustomSyntax;
|
|||||||
use crate::token::{
|
use crate::token::{
|
||||||
is_keyword_function, is_valid_identifier, Position, Token, TokenStream, NO_POS,
|
is_keyword_function, is_valid_identifier, Position, Token, TokenStream, NO_POS,
|
||||||
};
|
};
|
||||||
use crate::utils::{ImmutableString, StraightHasherBuilder};
|
use crate::utils::{get_hasher, ImmutableString, StraightHasherBuilder};
|
||||||
use crate::{calc_script_fn_hash, StaticVec};
|
use crate::{calc_script_fn_hash, StaticVec};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
use crate::ast::FloatWrapper;
|
use crate::FLOAT;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
use crate::engine::{make_getter, make_setter, KEYWORD_EVAL, KEYWORD_FN_PTR};
|
use crate::engine::{make_getter, make_setter, KEYWORD_EVAL, KEYWORD_FN_PTR};
|
||||||
@ -33,7 +33,7 @@ use crate::stdlib::{
|
|||||||
boxed::Box,
|
boxed::Box,
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
format,
|
format,
|
||||||
hash::Hash,
|
hash::{Hash, Hasher},
|
||||||
iter::empty,
|
iter::empty,
|
||||||
num::NonZeroUsize,
|
num::NonZeroUsize,
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
@ -41,28 +41,19 @@ use crate::stdlib::{
|
|||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
use crate::stdlib::hash::Hasher;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
use crate::stdlib::collections::HashSet;
|
use crate::stdlib::collections::HashSet;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_std"))]
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
use crate::stdlib::collections::hash_map::DefaultHasher;
|
|
||||||
|
|
||||||
#[cfg(feature = "no_std")]
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
use ahash::AHasher;
|
|
||||||
|
|
||||||
type PERR = ParseErrorType;
|
type PERR = ParseErrorType;
|
||||||
|
|
||||||
type FunctionsLib = HashMap<u64, ScriptFnDef, StraightHasherBuilder>;
|
type FunctionsLib = HashMap<u64, ScriptFnDef, StraightHasherBuilder>;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Debug)]
|
||||||
struct ParseState<'e> {
|
struct ParseState<'e> {
|
||||||
/// Reference to the scripting `Engine`.
|
/// Reference to the scripting `Engine`.
|
||||||
engine: &'e Engine,
|
engine: &'e Engine,
|
||||||
|
/// Hash that uniquely identifies a script.
|
||||||
|
script_hash: u64,
|
||||||
/// Interned strings.
|
/// Interned strings.
|
||||||
strings: HashMap<String, ImmutableString>,
|
strings: HashMap<String, ImmutableString>,
|
||||||
/// Encapsulates a local stack with variable names to simulate an actual runtime scope.
|
/// Encapsulates a local stack with variable names to simulate an actual runtime scope.
|
||||||
@ -93,6 +84,7 @@ impl<'e> ParseState<'e> {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
engine: &'e Engine,
|
engine: &'e Engine,
|
||||||
|
script_hash: u64,
|
||||||
#[cfg(not(feature = "unchecked"))] max_expr_depth: usize,
|
#[cfg(not(feature = "unchecked"))] max_expr_depth: usize,
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
@ -100,6 +92,7 @@ impl<'e> ParseState<'e> {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
engine,
|
engine,
|
||||||
|
script_hash,
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
max_expr_depth,
|
max_expr_depth,
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
@ -633,7 +626,9 @@ fn parse_array_literal(
|
|||||||
|
|
||||||
let mut arr = StaticVec::new();
|
let mut arr = StaticVec::new();
|
||||||
|
|
||||||
while !input.peek().unwrap().0.is_eof() {
|
loop {
|
||||||
|
const MISSING_RBRACKET: &str = "to end this array literal";
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
if state.engine.max_array_size() > 0 && arr.len() >= state.engine.max_array_size() {
|
if state.engine.max_array_size() > 0 && arr.len() >= state.engine.max_array_size() {
|
||||||
return Err(PERR::LiteralTooLarge(
|
return Err(PERR::LiteralTooLarge(
|
||||||
@ -648,6 +643,12 @@ fn parse_array_literal(
|
|||||||
eat_token(input, Token::RightBracket);
|
eat_token(input, Token::RightBracket);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
(Token::EOF, pos) => {
|
||||||
|
return Err(
|
||||||
|
PERR::MissingToken(Token::RightBracket.into(), MISSING_RBRACKET.into())
|
||||||
|
.into_err(*pos),
|
||||||
|
)
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
||||||
arr.push(expr);
|
arr.push(expr);
|
||||||
@ -660,11 +661,10 @@ fn parse_array_literal(
|
|||||||
}
|
}
|
||||||
(Token::RightBracket, _) => (),
|
(Token::RightBracket, _) => (),
|
||||||
(Token::EOF, pos) => {
|
(Token::EOF, pos) => {
|
||||||
return Err(PERR::MissingToken(
|
return Err(
|
||||||
Token::RightBracket.into(),
|
PERR::MissingToken(Token::RightBracket.into(), MISSING_RBRACKET.into())
|
||||||
"to end this array literal".into(),
|
.into_err(*pos),
|
||||||
)
|
)
|
||||||
.into_err(*pos))
|
|
||||||
}
|
}
|
||||||
(Token::LexError(err), pos) => return Err(err.into_err(*pos)),
|
(Token::LexError(err), pos) => return Err(err.into_err(*pos)),
|
||||||
(_, pos) => {
|
(_, pos) => {
|
||||||
@ -691,9 +691,9 @@ fn parse_map_literal(
|
|||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
||||||
|
|
||||||
let mut map = StaticVec::new();
|
let mut map: StaticVec<(IdentX, Expr)> = Default::default();
|
||||||
|
|
||||||
while !input.peek().unwrap().0.is_eof() {
|
loop {
|
||||||
const MISSING_RBRACE: &str = "to end this object map literal";
|
const MISSING_RBRACE: &str = "to end this object map literal";
|
||||||
|
|
||||||
match input.peek().unwrap() {
|
match input.peek().unwrap() {
|
||||||
@ -701,12 +701,22 @@ fn parse_map_literal(
|
|||||||
eat_token(input, Token::RightBrace);
|
eat_token(input, Token::RightBrace);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
(Token::EOF, pos) => {
|
||||||
|
return Err(
|
||||||
|
PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into())
|
||||||
|
.into_err(*pos),
|
||||||
|
)
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
let (name, pos) = match input.next().unwrap() {
|
let (name, pos) = match input.next().unwrap() {
|
||||||
(Token::Identifier(s), pos) => (s, pos),
|
(Token::Identifier(s), pos) | (Token::StringConstant(s), pos) => {
|
||||||
(Token::StringConstant(s), pos) => (s, pos),
|
if map.iter().any(|(p, _)| p.name == &s) {
|
||||||
|
return Err(PERR::DuplicatedProperty(s).into_err(pos));
|
||||||
|
}
|
||||||
|
(s, pos)
|
||||||
|
}
|
||||||
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
||||||
return Err(PERR::Reserved(s).into_err(pos));
|
return Err(PERR::Reserved(s).into_err(pos));
|
||||||
}
|
}
|
||||||
@ -775,20 +785,128 @@ fn parse_map_literal(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for duplicating properties
|
|
||||||
map.iter()
|
|
||||||
.enumerate()
|
|
||||||
.try_for_each(|(i, (IdentX { name: k1, .. }, _))| {
|
|
||||||
map.iter()
|
|
||||||
.skip(i + 1)
|
|
||||||
.find(|(IdentX { name: k2, .. }, _)| k2 == k1)
|
|
||||||
.map_or_else(|| Ok(()), |(IdentX { name: k2, pos }, _)| Err((k2, *pos)))
|
|
||||||
})
|
|
||||||
.map_err(|(key, pos)| PERR::DuplicatedProperty(key.to_string()).into_err(pos))?;
|
|
||||||
|
|
||||||
Ok(Expr::Map(Box::new(map), settings.pos))
|
Ok(Expr::Map(Box::new(map), settings.pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a switch expression.
|
||||||
|
fn parse_switch(
|
||||||
|
input: &mut TokenStream,
|
||||||
|
state: &mut ParseState,
|
||||||
|
lib: &mut FunctionsLib,
|
||||||
|
settings: ParseSettings,
|
||||||
|
) -> Result<Expr, ParseError> {
|
||||||
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
||||||
|
|
||||||
|
let item = parse_expr(input, state, lib, settings.level_up())?;
|
||||||
|
|
||||||
|
match input.next().unwrap() {
|
||||||
|
(Token::LeftBrace, _) => (),
|
||||||
|
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||||
|
(_, pos) => {
|
||||||
|
return Err(PERR::MissingToken(
|
||||||
|
Token::LeftBrace.into(),
|
||||||
|
"to start a switch block".into(),
|
||||||
|
)
|
||||||
|
.into_err(pos))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut table: HashMap<u64, Stmt, StraightHasherBuilder> = Default::default();
|
||||||
|
let mut def_stmt = None;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
const MISSING_RBRACE: &str = "to end this switch block";
|
||||||
|
|
||||||
|
let expr = match input.peek().unwrap() {
|
||||||
|
(Token::RightBrace, _) => {
|
||||||
|
eat_token(input, Token::RightBrace);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
(Token::EOF, pos) => {
|
||||||
|
return Err(
|
||||||
|
PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into())
|
||||||
|
.into_err(*pos),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
(Token::Underscore, _) if def_stmt.is_none() => {
|
||||||
|
eat_token(input, Token::Underscore);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
(Token::Underscore, pos) => return Err(PERR::DuplicatedSwitchCase.into_err(*pos)),
|
||||||
|
_ => Some(parse_expr(input, state, lib, settings.level_up())?),
|
||||||
|
};
|
||||||
|
|
||||||
|
let hash = if let Some(expr) = expr {
|
||||||
|
if let Some(value) = expr.get_constant_value() {
|
||||||
|
let hasher = &mut get_hasher();
|
||||||
|
value.hash(hasher);
|
||||||
|
let hash = hasher.finish();
|
||||||
|
|
||||||
|
if table.contains_key(&hash) {
|
||||||
|
return Err(PERR::DuplicatedSwitchCase.into_err(expr.position()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(hash)
|
||||||
|
} else {
|
||||||
|
return Err(PERR::ExprExpected("a literal".to_string()).into_err(expr.position()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
match input.next().unwrap() {
|
||||||
|
(Token::DoubleArrow, _) => (),
|
||||||
|
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||||
|
(_, pos) => {
|
||||||
|
return Err(PERR::MissingToken(
|
||||||
|
Token::DoubleArrow.into(),
|
||||||
|
"in this switch case".to_string(),
|
||||||
|
)
|
||||||
|
.into_err(pos))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let stmt = parse_stmt(input, state, lib, settings.level_up()).map(Option::unwrap)?;
|
||||||
|
|
||||||
|
let need_comma = !stmt.is_self_terminated();
|
||||||
|
|
||||||
|
def_stmt = if let Some(hash) = hash {
|
||||||
|
table.insert(hash, stmt);
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(stmt)
|
||||||
|
};
|
||||||
|
|
||||||
|
match input.peek().unwrap() {
|
||||||
|
(Token::Comma, _) => {
|
||||||
|
eat_token(input, Token::Comma);
|
||||||
|
}
|
||||||
|
(Token::RightBrace, _) => (),
|
||||||
|
(Token::EOF, pos) => {
|
||||||
|
return Err(
|
||||||
|
PERR::MissingToken(Token::RightParen.into(), MISSING_RBRACE.into())
|
||||||
|
.into_err(*pos),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
(Token::LexError(err), pos) => return Err(err.into_err(*pos)),
|
||||||
|
(_, pos) if need_comma => {
|
||||||
|
return Err(PERR::MissingToken(
|
||||||
|
Token::Comma.into(),
|
||||||
|
"to separate the items in this switch block".into(),
|
||||||
|
)
|
||||||
|
.into_err(*pos))
|
||||||
|
}
|
||||||
|
(_, _) => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Expr::Switch(
|
||||||
|
Box::new((item, table, def_stmt)),
|
||||||
|
settings.pos,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse a primary expression.
|
/// Parse a primary expression.
|
||||||
fn parse_primary(
|
fn parse_primary(
|
||||||
input: &mut TokenStream,
|
input: &mut TokenStream,
|
||||||
@ -819,7 +937,7 @@ fn parse_primary(
|
|||||||
let mut root_expr = match token {
|
let mut root_expr = match token {
|
||||||
Token::IntegerConstant(x) => Expr::IntegerConstant(x, settings.pos),
|
Token::IntegerConstant(x) => Expr::IntegerConstant(x, settings.pos),
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Token::FloatConstant(x) => Expr::FloatConstant(FloatWrapper(x), settings.pos),
|
Token::FloatConstant(x) => Expr::FloatConstant(x, settings.pos),
|
||||||
Token::CharConstant(c) => Expr::CharConstant(c, settings.pos),
|
Token::CharConstant(c) => Expr::CharConstant(c, settings.pos),
|
||||||
Token::StringConstant(s) => {
|
Token::StringConstant(s) => {
|
||||||
Expr::StringConstant(state.get_interned_string(s), settings.pos)
|
Expr::StringConstant(state.get_interned_string(s), settings.pos)
|
||||||
@ -886,6 +1004,7 @@ fn parse_primary(
|
|||||||
Token::LeftBracket => parse_array_literal(input, state, lib, settings.level_up())?,
|
Token::LeftBracket => parse_array_literal(input, state, lib, settings.level_up())?,
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Token::MapStart => parse_map_literal(input, state, lib, settings.level_up())?,
|
Token::MapStart => parse_map_literal(input, state, lib, settings.level_up())?,
|
||||||
|
Token::Switch => parse_switch(input, state, lib, settings.level_up())?,
|
||||||
Token::True => Expr::True(settings.pos),
|
Token::True => Expr::True(settings.pos),
|
||||||
Token::False => Expr::False(settings.pos),
|
Token::False => Expr::False(settings.pos),
|
||||||
Token::LexError(err) => return Err(err.into_err(settings.pos)),
|
Token::LexError(err) => return Err(err.into_err(settings.pos)),
|
||||||
@ -1028,7 +1147,7 @@ fn parse_unary(
|
|||||||
.map(|i| Expr::IntegerConstant(i, pos))
|
.map(|i| Expr::IntegerConstant(i, pos))
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
return Some(Expr::FloatConstant(-Into::<FloatWrapper>::into(num), pos));
|
return Some(Expr::FloatConstant(-(num as FLOAT), pos));
|
||||||
#[cfg(feature = "no_float")]
|
#[cfg(feature = "no_float")]
|
||||||
return None;
|
return None;
|
||||||
})
|
})
|
||||||
@ -1091,6 +1210,7 @@ fn parse_unary(
|
|||||||
Token::Pipe | Token::Or if settings.allow_anonymous_fn => {
|
Token::Pipe | Token::Or if settings.allow_anonymous_fn => {
|
||||||
let mut new_state = ParseState::new(
|
let mut new_state = ParseState::new(
|
||||||
state.engine,
|
state.engine,
|
||||||
|
state.script_hash,
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
state.max_function_expr_depth,
|
state.max_function_expr_depth,
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
@ -2162,10 +2282,7 @@ fn parse_block(
|
|||||||
// Parse statements inside the block
|
// Parse statements inside the block
|
||||||
settings.is_global = false;
|
settings.is_global = false;
|
||||||
|
|
||||||
let stmt = match parse_stmt(input, state, lib, settings.level_up())? {
|
let stmt = parse_stmt(input, state, lib, settings.level_up()).map(Option::unwrap)?;
|
||||||
Some(s) => s,
|
|
||||||
None => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
// See if it needs a terminating semicolon
|
// See if it needs a terminating semicolon
|
||||||
let need_semicolon = !stmt.is_self_terminated();
|
let need_semicolon = !stmt.is_self_terminated();
|
||||||
@ -2266,6 +2383,7 @@ fn parse_stmt(
|
|||||||
(Token::Fn, pos) => {
|
(Token::Fn, pos) => {
|
||||||
let mut new_state = ParseState::new(
|
let mut new_state = ParseState::new(
|
||||||
state.engine,
|
state.engine,
|
||||||
|
state.script_hash,
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
state.max_function_expr_depth,
|
state.max_function_expr_depth,
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
@ -2471,6 +2589,9 @@ fn parse_fn(
|
|||||||
match input.next().unwrap() {
|
match input.next().unwrap() {
|
||||||
(Token::RightParen, _) => break,
|
(Token::RightParen, _) => break,
|
||||||
(Token::Identifier(s), pos) => {
|
(Token::Identifier(s), pos) => {
|
||||||
|
if params.iter().any(|(p, _)| p == &s) {
|
||||||
|
return Err(PERR::FnDuplicatedParam(name.to_string(), s).into_err(pos));
|
||||||
|
}
|
||||||
let s = state.get_interned_string(s);
|
let s = state.get_interned_string(s);
|
||||||
state.stack.push((s.clone(), ScopeEntryType::Normal));
|
state.stack.push((s.clone(), ScopeEntryType::Normal));
|
||||||
params.push((s, pos))
|
params.push((s, pos))
|
||||||
@ -2496,21 +2617,6 @@ fn parse_fn(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for duplicating parameters
|
|
||||||
params
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.try_for_each(|(i, (p1, _))| {
|
|
||||||
params
|
|
||||||
.iter()
|
|
||||||
.skip(i + 1)
|
|
||||||
.find(|(p2, _)| p2 == p1)
|
|
||||||
.map_or_else(|| Ok(()), |(p2, pos)| Err((p2, *pos)))
|
|
||||||
})
|
|
||||||
.map_err(|(p, pos)| {
|
|
||||||
PERR::FnDuplicatedParam(name.to_string(), p.to_string()).into_err(pos)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Parse function body
|
// Parse function body
|
||||||
let body = match input.peek().unwrap() {
|
let body = match input.peek().unwrap() {
|
||||||
(Token::LeftBrace, _) => {
|
(Token::LeftBrace, _) => {
|
||||||
@ -2614,6 +2720,9 @@ fn parse_anon_fn(
|
|||||||
match input.next().unwrap() {
|
match input.next().unwrap() {
|
||||||
(Token::Pipe, _) => break,
|
(Token::Pipe, _) => break,
|
||||||
(Token::Identifier(s), pos) => {
|
(Token::Identifier(s), pos) => {
|
||||||
|
if params.iter().any(|(p, _)| p == &s) {
|
||||||
|
return Err(PERR::FnDuplicatedParam("".to_string(), s).into_err(pos));
|
||||||
|
}
|
||||||
let s = state.get_interned_string(s);
|
let s = state.get_interned_string(s);
|
||||||
state.stack.push((s.clone(), ScopeEntryType::Normal));
|
state.stack.push((s.clone(), ScopeEntryType::Normal));
|
||||||
params.push((s, pos))
|
params.push((s, pos))
|
||||||
@ -2644,24 +2753,9 @@ fn parse_anon_fn(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for duplicating parameters
|
|
||||||
params
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.try_for_each(|(i, (p1, _))| {
|
|
||||||
params
|
|
||||||
.iter()
|
|
||||||
.skip(i + 1)
|
|
||||||
.find(|(p2, _)| p2 == p1)
|
|
||||||
.map_or_else(|| Ok(()), |(p2, pos)| Err((p2, *pos)))
|
|
||||||
})
|
|
||||||
.map_err(|(p, pos)| PERR::FnDuplicatedParam("".to_string(), p.to_string()).into_err(pos))?;
|
|
||||||
|
|
||||||
// Parse function body
|
// Parse function body
|
||||||
settings.is_breakable = false;
|
settings.is_breakable = false;
|
||||||
let pos = input.peek().unwrap().1;
|
let body = parse_stmt(input, state, lib, settings.level_up()).map(Option::unwrap)?;
|
||||||
let body = parse_stmt(input, state, lib, settings.level_up())
|
|
||||||
.map(|stmt| stmt.unwrap_or_else(|| Stmt::Noop(pos)))?;
|
|
||||||
|
|
||||||
// External variables may need to be processed in a consistent order,
|
// External variables may need to be processed in a consistent order,
|
||||||
// so extract them into a list.
|
// so extract them into a list.
|
||||||
@ -2688,18 +2782,12 @@ fn parse_anon_fn(
|
|||||||
params.into_iter().map(|(v, _)| v).collect()
|
params.into_iter().map(|(v, _)| v).collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Calculate hash
|
// Create unique function name by hashing the script hash plus the position
|
||||||
#[cfg(feature = "no_std")]
|
let hasher = &mut get_hasher();
|
||||||
let mut s: AHasher = Default::default();
|
state.script_hash.hash(hasher);
|
||||||
#[cfg(not(feature = "no_std"))]
|
settings.pos.hash(hasher);
|
||||||
let mut s = DefaultHasher::new();
|
let hash = hasher.finish();
|
||||||
|
|
||||||
s.write_usize(params.len());
|
|
||||||
params.iter().for_each(|a| a.hash(&mut s));
|
|
||||||
body.hash(&mut s);
|
|
||||||
let hash = s.finish();
|
|
||||||
|
|
||||||
// Create unique function name
|
|
||||||
let fn_name: ImmutableString = format!("{}{:016x}", FN_ANONYMOUS, hash).into();
|
let fn_name: ImmutableString = format!("{}{:016x}", FN_ANONYMOUS, hash).into();
|
||||||
|
|
||||||
// Define the function
|
// Define the function
|
||||||
@ -2729,6 +2817,7 @@ fn parse_anon_fn(
|
|||||||
impl Engine {
|
impl Engine {
|
||||||
pub(crate) fn parse_global_expr(
|
pub(crate) fn parse_global_expr(
|
||||||
&self,
|
&self,
|
||||||
|
script_hash: u64,
|
||||||
input: &mut TokenStream,
|
input: &mut TokenStream,
|
||||||
scope: &Scope,
|
scope: &Scope,
|
||||||
optimization_level: OptimizationLevel,
|
optimization_level: OptimizationLevel,
|
||||||
@ -2736,6 +2825,7 @@ impl Engine {
|
|||||||
let mut functions = Default::default();
|
let mut functions = Default::default();
|
||||||
let mut state = ParseState::new(
|
let mut state = ParseState::new(
|
||||||
self,
|
self,
|
||||||
|
script_hash,
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.max_expr_depth(),
|
self.max_expr_depth(),
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
@ -2779,12 +2869,14 @@ impl Engine {
|
|||||||
/// Parse the global level statements.
|
/// Parse the global level statements.
|
||||||
fn parse_global_level(
|
fn parse_global_level(
|
||||||
&self,
|
&self,
|
||||||
|
script_hash: u64,
|
||||||
input: &mut TokenStream,
|
input: &mut TokenStream,
|
||||||
) -> Result<(Vec<Stmt>, Vec<ScriptFnDef>), ParseError> {
|
) -> Result<(Vec<Stmt>, Vec<ScriptFnDef>), ParseError> {
|
||||||
let mut statements: Vec<Stmt> = Default::default();
|
let mut statements: Vec<Stmt> = Default::default();
|
||||||
let mut functions = Default::default();
|
let mut functions = Default::default();
|
||||||
let mut state = ParseState::new(
|
let mut state = ParseState::new(
|
||||||
self,
|
self,
|
||||||
|
script_hash,
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.max_expr_depth(),
|
self.max_expr_depth(),
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
@ -2845,11 +2937,12 @@ impl Engine {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn parse(
|
pub(crate) fn parse(
|
||||||
&self,
|
&self,
|
||||||
|
script_hash: u64,
|
||||||
input: &mut TokenStream,
|
input: &mut TokenStream,
|
||||||
scope: &Scope,
|
scope: &Scope,
|
||||||
optimization_level: OptimizationLevel,
|
optimization_level: OptimizationLevel,
|
||||||
) -> Result<AST, ParseError> {
|
) -> Result<AST, ParseError> {
|
||||||
let (statements, lib) = self.parse_global_level(input)?;
|
let (statements, lib) = self.parse_global_level(script_hash, input)?;
|
||||||
|
|
||||||
Ok(
|
Ok(
|
||||||
// Optimize AST
|
// Optimize AST
|
||||||
@ -2864,7 +2957,7 @@ impl Engine {
|
|||||||
pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option<Expr> {
|
pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option<Expr> {
|
||||||
match value.0 {
|
match value.0 {
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Union::Float(value) => Some(Expr::FloatConstant(FloatWrapper(value), pos)),
|
Union::Float(value) => Some(Expr::FloatConstant(value, pos)),
|
||||||
|
|
||||||
Union::Unit(_) => Some(Expr::Unit(pos)),
|
Union::Unit(_) => Some(Expr::Unit(pos)),
|
||||||
Union::Int(value) => Some(Expr::IntegerConstant(value, pos)),
|
Union::Int(value) => Some(Expr::IntegerConstant(value, pos)),
|
||||||
|
@ -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> {
|
||||||
|
39
src/token.rs
39
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(),
|
||||||
)),
|
)),
|
||||||
|
15
src/utils.rs
15
src/utils.rs
@ -92,6 +92,16 @@ 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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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.
|
/// 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.
|
||||||
@ -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));
|
||||||
|
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