Merge pull request #292 from schungx/master

Switch expression.
This commit is contained in:
Stephen Chung 2020-11-13 19:25:58 +08:00 committed by GitHub
commit 4f4626cc25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1216 additions and 900 deletions

View File

@ -6,7 +6,7 @@ members = [
[package]
name = "rhai"
version = "0.19.5"
version = "0.19.6"
edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
description = "Embedded scripting for Rust"

View File

@ -1,6 +1,17 @@
Rhai Release Notes
==================
Version 0.19.6
==============
This version adds the `switch` statement.
New features
------------
* `switch` statement.
Version 0.19.5
==============
@ -209,7 +220,7 @@ Bug fixes
---------
* 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
------------
@ -299,7 +310,7 @@ New features
* The boolean `^` (XOR) operator is added.
* `FnPtr` is exposed as the function pointer type.
* `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.
* `String` parameters in functions are supported (but inefficiently).

View File

@ -77,13 +77,14 @@ The Rhai Scripting Language
7. [Logic Operators](language/logic.md)
8. [Other Operators](language/other-op.md)
9. [If Statement](language/if.md)
10. [While Loop](language/while.md)
11. [Loop Statement](language/loop.md)
12. [For Loop](language/for.md)
13. [Return Values](language/return.md)
14. [Throw Exception on Error](language/throw.md)
15. [Catch Exceptions](language/try-catch.md)
16. [Functions](language/functions.md)
10. [Switch Expression](language/switch.md)
11. [While Loop](language/while.md)
12. [Loop Statement](language/loop.md)
13. [For Loop](language/for.md)
14. [Return Values](language/return.md)
15. [Throw Exception on Error](language/throw.md)
16. [Catch Exceptions](language/try-catch.md)
17. [Functions](language/functions.md)
1. [Call Method as Function](language/method.md)
2. [Overloading](language/overload.md)
3. [Namespaces](language/fn-namespaces.md)
@ -91,11 +92,11 @@ The Rhai Scripting Language
5. [Currying](language/fn-curry.md)
6. [Anonymous Functions](language/fn-anon.md)
7. [Closures](language/fn-closure.md)
17. [Print and Debug](language/print-debug.md)
18. [Modules](language/modules/index.md)
18. [Print and Debug](language/print-debug.md)
19. [Modules](language/modules/index.md)
1. [Export Variables, Functions and Sub-Modules](language/modules/export.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)
1. [Checked Arithmetic](safety/checked.md)
2. [Sand-Boxing](safety/sandbox.md)

View File

@ -13,6 +13,7 @@ Keywords List
| `is_shared` | is a value shared? | [`no_closure`] | yes | no |
| `if` | if statement | | no | |
| `else` | else block of if statement | | no | |
| `switch` | matching | | no | |
| `while` | while loop | | no | |
| `loop` | infinite loop | | no | |
| `for` | for loop | | no | |
@ -52,7 +53,6 @@ Reserved Keywords
| `then` | control flow |
| `goto` | control flow |
| `exit` | control flow |
| `switch` | matching |
| `match` | matching |
| `case` | matching |
| `public` | function/field access |

View File

@ -39,6 +39,7 @@ Symbols and Patterns
| Symbol | Name | Description |
| ---------------------------------- | :------------------: | ------------------------------------- |
| `_` | underscore | default `switch` case |
| `;` | semicolon | statement separator |
| `,` | comma | list separator |
| `:` | colon | [object map] property value separator |
@ -52,6 +53,7 @@ Symbols and Patterns
| <code>\|</code> .. <code>\|</code> | pipes | closure |
| `[` .. `]` | brackets | [array] literal |
| `!` | bang | function call in calling scope |
| `=>` | double arrow | `switch` expression case separator |
| `//` | comment | line comment |
| `/*` .. `*/` | comment | block comment |
| `(*` .. `*)` | comment | _reserved_ |
@ -64,7 +66,6 @@ Symbols and Patterns
| `#` | hash | _reserved_ |
| `@` | at | _reserved_ |
| `$` | dollar | _reserved_ |
| `=>` | double arrow | _reserved_ |
| `->` | arrow | _reserved_ |
| `<-` | left arrow | _reserved_ |
| `===` | strict equals to | _reserved_ |

View File

@ -1,5 +1,5 @@
{
"version": "0.19.5",
"version": "0.19.6",
"repoHome": "https://github.com/jonathandturner/rhai/blob/master",
"repoTree": "https://github.com/jonathandturner/rhai/tree/master",
"rootUrl": "",

View File

@ -49,7 +49,7 @@ For example:
let animal = "rabbit";
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
```
@ -61,7 +61,7 @@ nevertheless it makes the DSL syntax much simpler and expressive.
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.
For example, the following is a SQL-like syntax for some obscure DSL operation:

View File

@ -1,5 +1,5 @@
`Scope` - Initializing and Maintaining State
===========================================
=================================================
{{#include ../links.md}}

View File

@ -12,27 +12,19 @@ Use `type_of()` to Get Value Type
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.
```rust
```c
let mystery = get_some_dynamic_value();
if type_of(mystery) == "i64" {
print("Hey, I got an integer here!");
} else if type_of(mystery) == "f64" {
print("Hey, I got a float here!");
} else if type_of(mystery) == "string" {
print("Hey, I got a string here!");
} else if type_of(mystery) == "bool" {
print("Hey, I got a boolean here!");
} else if type_of(mystery) == "array" {
print("Hey, I got an array here!");
} 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));
switch mystery {
"i64" => print("Hey, I got an integer here!"),
"f64" => print("Hey, I got a float here!"),
"string" => print("Hey, I got a string here!"),
"bool" => print("Hey, I got a boolean here!"),
"array" => print("Hey, I got an array here!"),
"map" => print("Hey, I got an object map here!"),
"Fn" => print("Hey, I got a function pointer here!"),
"TestStruct" => print("Hey, I got the TestStruct custom type here!"),
_ => print("I don't know what this is: " + type_of(mystery))
}
```

View File

@ -15,10 +15,10 @@ 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:
| 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 |
| Module | [`Module`] | module-qualified function name | yes | yes |
| 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 |
| Module | [`Module`] | namespace-qualified function name | yes | yes |
Global Namespace

View 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.

View File

@ -69,9 +69,9 @@ resolver.insert("question", module);
let mut engine = Engine::new();
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;
// Call module-qualified functions
// Call namespace-qualified functions
engine.eval::<i64>(r#"import "question" as q; q::inc(q::answer)"#)? == 42;
```

View File

@ -2,10 +2,10 @@
use crate::dynamic::{Dynamic, Union};
use crate::fn_native::{FnPtr, Shared};
use crate::module::{Module, ModuleRef};
use crate::module::{Module, NamespaceRef};
use crate::syntax::FnCustomSyntaxEval;
use crate::token::{Position, Token, NO_POS};
use crate::utils::ImmutableString;
use crate::utils::{ImmutableString, StraightHasherBuilder};
use crate::StaticVec;
use crate::INT;
@ -16,7 +16,7 @@ use crate::FLOAT;
use crate::engine::Array;
#[cfg(not(feature = "no_object"))]
use crate::engine::{make_getter, make_setter, Map};
use crate::engine::Map;
#[cfg(not(feature = "no_module"))]
use crate::engine::Imports;
@ -25,8 +25,9 @@ use crate::stdlib::{
any::TypeId,
borrow::Cow,
boxed::Box,
collections::HashMap,
fmt,
hash::{Hash, Hasher},
hash::Hash,
num::NonZeroUsize,
ops::{Add, AddAssign},
string::String,
@ -34,9 +35,6 @@ use crate::stdlib::{
vec::Vec,
};
#[cfg(not(feature = "no_float"))]
use crate::stdlib::ops::Neg;
#[cfg(not(feature = "no_closure"))]
use crate::stdlib::collections::HashSet;
@ -98,10 +96,10 @@ pub struct ScriptFnDef {
/// Function access mode.
pub access: FnAccess,
/// Names of function parameters.
pub params: StaticVec<String>,
pub params: StaticVec<ImmutableString>,
/// Access to external variables.
#[cfg(not(feature = "no_closure"))]
pub externals: HashSet<String>,
pub externals: HashSet<ImmutableString>,
}
impl fmt::Display for ScriptFnDef {
@ -602,7 +600,7 @@ pub enum ReturnType {
/// ## WARNING
///
/// This type is volatile and may change.
#[derive(Debug, Clone, Hash)]
#[derive(Debug, Clone)]
pub enum Stmt {
/// No-op.
Noop(Position),
@ -637,10 +635,10 @@ pub enum Stmt {
Import(Expr, Option<Box<IdentX>>, Position),
/// export var as var, ...
#[cfg(not(feature = "no_module"))]
Export(Vec<(Ident, Option<Ident>)>, Position),
Export(Vec<(IdentX, Option<IdentX>)>, Position),
/// Convert a variable to shared.
#[cfg(not(feature = "no_closure"))]
Share(Box<Ident>),
Share(IdentX),
}
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.
///
/// ## 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 {
/// Get the keywords for this `CustomExpr`.
#[inline(always)]
@ -819,50 +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
/// 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.
/// _[INTERNALS]_ A binary expression.
/// Exported under the `internals` feature only.
///
/// ## WARNING
///
/// This type is volatile and may change.
#[derive(Debug, Clone, Hash)]
#[derive(Debug, Clone)]
pub struct BinaryExpr {
/// LHS expression.
pub lhs: Expr,
@ -876,8 +830,8 @@ pub struct BinaryExpr {
/// ## WARNING
///
/// This type is volatile and may change.
#[derive(Debug, Clone, Hash, Default)]
pub struct FnCallInfo {
#[derive(Debug, Clone, Default)]
pub struct FnCallExpr {
/// Pre-calculated hash for a script-defined function of the same name and number of parameters.
pub hash: u64,
/// 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`
pub def_value: Option<bool>,
/// Namespace of the function, if any. Boxed because it occurs rarely.
pub namespace: Option<Box<ModuleRef>>,
pub namespace: Option<Box<NamespaceRef>>,
/// Function name.
/// 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`.
@ -904,49 +858,58 @@ pub struct FnCallInfo {
/// ## WARNING
///
/// This type is volatile and may change.
#[derive(Debug, Clone, Hash)]
#[derive(Debug, Clone)]
pub enum Expr {
/// Integer constant.
IntegerConstant(INT, Position),
/// Floating-point constant.
#[cfg(not(feature = "no_float"))]
FloatConstant(FloatWrapper, Position),
FloatConstant(FLOAT, Position),
/// Character constant.
CharConstant(char, Position),
/// String constant.
StringConstant(Box<IdentX>),
StringConstant(ImmutableString, Position),
/// FnPtr constant.
FnPointer(Box<IdentX>),
/// 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),
FnPointer(ImmutableString, Position),
/// [ expr, ... ]
Array(Box<StaticVec<Expr>>, Position),
/// #{ name:expr, ... }
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(Position),
/// false
False(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(Box<CustomExpr>, Position),
}
@ -970,8 +933,8 @@ impl Expr {
#[cfg(not(feature = "no_float"))]
Self::FloatConstant(_, _) => TypeId::of::<FLOAT>(),
Self::CharConstant(_, _) => TypeId::of::<char>(),
Self::StringConstant(_) => TypeId::of::<ImmutableString>(),
Self::FnPointer(_) => TypeId::of::<FnPtr>(),
Self::StringConstant(_, _) => TypeId::of::<ImmutableString>(),
Self::FnPointer(_, _) => TypeId::of::<FnPtr>(),
Self::True(_) | Self::False(_) | Self::In(_, _) | Self::And(_, _) | Self::Or(_, _) => {
TypeId::of::<bool>()
}
@ -996,11 +959,11 @@ impl Expr {
Self::IntegerConstant(x, _) => (*x).into(),
#[cfg(not(feature = "no_float"))]
Self::FloatConstant(x, _) => x.0.into(),
Self::FloatConstant(x, _) => (*x).into(),
Self::CharConstant(x, _) => (*x).into(),
Self::StringConstant(x) => x.name.clone().into(),
Self::FnPointer(x) => Dynamic(Union::FnPtr(Box::new(FnPtr::new_unchecked(
x.name.clone(),
Self::StringConstant(x, _) => x.clone().into(),
Self::FnPointer(x, _) => Dynamic(Union::FnPtr(Box::new(FnPtr::new_unchecked(
x.clone(),
Default::default(),
)))),
Self::True(_) => true.into(),
@ -1043,14 +1006,15 @@ impl Expr {
Self::IntegerConstant(_, pos) => *pos,
Self::CharConstant(_, pos) => *pos,
Self::StringConstant(x) => x.pos,
Self::FnPointer(x) => x.pos,
Self::StringConstant(_, pos) => *pos,
Self::FnPointer(_, pos) => *pos,
Self::Array(_, pos) => *pos,
Self::Map(_, pos) => *pos,
Self::Property(x) => (x.1).pos,
Self::Stmt(_, pos) => *pos,
Self::Variable(x) => (x.3).pos,
Self::FnCall(_, pos) => *pos,
Self::Switch(_, pos) => *pos,
Self::And(x, _) | Self::Or(x, _) | Self::In(x, _) => x.lhs.position(),
@ -1074,14 +1038,15 @@ impl Expr {
Self::IntegerConstant(_, pos) => *pos = new_pos,
Self::CharConstant(_, pos) => *pos = new_pos,
Self::StringConstant(x) => x.pos = new_pos,
Self::FnPointer(x) => x.pos = new_pos,
Self::StringConstant(_, pos) => *pos = new_pos,
Self::FnPointer(_, pos) => *pos = new_pos,
Self::Array(_, pos) => *pos = new_pos,
Self::Map(_, pos) => *pos = new_pos,
Self::Variable(x) => (x.3).pos = new_pos,
Self::Property(x) => (x.1).pos = new_pos,
Self::Stmt(_, pos) => *pos = new_pos,
Self::FnCall(_, pos) => *pos = new_pos,
Self::Switch(_, pos) => *pos = new_pos,
Self::And(_, pos) | Self::Or(_, pos) | Self::In(_, pos) => *pos = new_pos,
Self::True(pos) | Self::False(pos) | Self::Unit(pos) => *pos = new_pos,
Self::Dot(_, pos) | Self::Index(_, pos) => *pos = new_pos,
@ -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?
pub fn is_constant(&self) -> bool {
match self {
@ -1166,8 +1098,8 @@ impl Expr {
Self::IntegerConstant(_, _)
| Self::CharConstant(_, _)
| Self::StringConstant(_)
| Self::FnPointer(_)
| Self::StringConstant(_, _)
| Self::FnPointer(_, _)
| Self::True(_)
| Self::False(_)
| Self::Unit(_) => true,
@ -1180,8 +1112,8 @@ impl Expr {
// Check in expression
Self::In(x, _) => match (&x.lhs, &x.rhs) {
(Self::StringConstant(_), Self::StringConstant(_))
| (Self::CharConstant(_, _), Self::StringConstant(_)) => true,
(Self::StringConstant(_, _), Self::StringConstant(_, _))
| (Self::CharConstant(_, _), Self::StringConstant(_, _)) => true,
_ => false,
},
@ -1199,7 +1131,7 @@ impl Expr {
Self::IntegerConstant(_, _)
| Self::CharConstant(_, _)
| Self::FnPointer(_)
| Self::FnPointer(_, _)
| Self::In(_, _)
| Self::And(_, _)
| Self::Or(_, _)
@ -1207,9 +1139,10 @@ impl Expr {
| Self::False(_)
| Self::Unit(_) => false,
Self::StringConstant(_)
Self::StringConstant(_, _)
| Self::Stmt(_, _)
| Self::FnCall(_, _)
| Self::Switch(_, _)
| Self::Dot(_, _)
| Self::Index(_, _)
| Self::Array(_, _)
@ -1238,21 +1171,6 @@ impl Expr {
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)]

View File

@ -3,7 +3,7 @@
use crate::fn_native::{FnPtr, SendSync};
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
use crate::utils::ImmutableString;
use crate::INT;
use crate::{StaticVec, INT};
#[cfg(not(feature = "no_closure"))]
use crate::fn_native::{shared_try_take, Locked, Shared};
@ -21,6 +21,7 @@ use crate::stdlib::{
any::{type_name, Any, TypeId},
boxed::Box,
fmt,
hash::{Hash, Hasher},
ops::{Deref, DerefMut},
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.
#[inline]
pub(crate) fn map_std_type_name(name: &str) -> &str {

View File

@ -1,10 +1,10 @@
//! 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::fn_call::run_builtin_op_assignment;
use crate::fn_native::{Callback, FnPtr, OnVarCallback, Shared};
use crate::module::{Module, ModuleRef};
use crate::module::{Module, NamespaceRef};
use crate::optimize::OptimizationLevel;
use crate::packages::{Package, PackagesCollection, StandardPackage};
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::syntax::CustomSyntax;
use crate::token::{Position, NO_POS};
use crate::utils::get_hasher;
use crate::{calc_native_fn_hash, StaticVec};
#[cfg(not(feature = "no_index"))]
@ -38,6 +39,7 @@ use crate::stdlib::{
boxed::Box,
collections::{HashMap, HashSet},
fmt, format,
hash::{Hash, Hasher},
iter::{empty, once},
num::NonZeroUsize,
ops::DerefMut,
@ -593,7 +595,7 @@ pub struct Engine {
/// Max limits.
#[cfg(not(feature = "unchecked"))]
pub(crate) limits_set: Limits,
pub(crate) limits: Limits,
}
impl fmt::Debug for Engine {
@ -636,6 +638,7 @@ pub fn is_anonymous_fn(fn_name: &str) -> bool {
}
/// Print/debug to stdout
#[inline(always)]
fn default_print(_s: &str) {
#[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
@ -647,15 +650,15 @@ fn default_print(_s: &str) {
pub fn search_imports(
mods: &Imports,
state: &mut State,
modules: &ModuleRef,
namespace: &NamespaceRef,
) -> 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
let index = if state.always_search {
0
} else {
modules.index().map_or(0, NonZeroUsize::get)
namespace.index().map_or(0, NonZeroUsize::get)
};
Ok(if index > 0 {
@ -670,7 +673,7 @@ pub fn search_imports(
impl Engine {
/// Create a new `Engine`
#[inline(always)]
#[inline]
pub fn new() -> Self {
// Create the new scripting Engine
let mut engine = Self {
@ -710,7 +713,7 @@ impl Engine {
},
#[cfg(not(feature = "unchecked"))]
limits_set: Limits {
limits: Limits {
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
max_expr_depth: MAX_EXPR_DEPTH,
#[cfg(not(feature = "no_function"))]
@ -733,7 +736,7 @@ impl Engine {
/// Create a new `Engine` with minimal built-in functions.
/// Use the `load_package` method to load additional packages of functions.
#[inline(always)]
#[inline]
pub fn new_raw() -> Self {
Self {
id: Default::default(),
@ -762,7 +765,7 @@ impl Engine {
},
#[cfg(not(feature = "unchecked"))]
limits_set: Limits {
limits: Limits {
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
max_expr_depth: MAX_EXPR_DEPTH,
#[cfg(not(feature = "no_function"))]
@ -780,7 +783,7 @@ impl Engine {
}
/// 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>(
&self,
scope: &'s mut Scope,
@ -793,7 +796,7 @@ impl Engine {
match expr {
Expr::Variable(v) => match v.as_ref() {
// Qualified variable
(_, Some(modules), hash_var, Ident { name, pos }) => {
(_, Some(modules), hash_var, IdentX { name, pos }) => {
let module = search_imports(mods, state, modules)?;
let target = module.get_qualified_var(*hash_var).map_err(|mut err| {
match *err {
@ -825,13 +828,13 @@ impl Engine {
this_ptr: &'s mut Option<&mut Dynamic>,
expr: &'a Expr,
) -> 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(),
_ => unreachable!(),
};
// Check if the variable is `this`
if name == KEYWORD_THIS {
if name.as_str() == KEYWORD_THIS {
if let Some(val) = this_ptr {
return Ok(((*val).into(), KEYWORD_THIS, ScopeEntryType::Normal, *pos));
} else {
@ -870,7 +873,7 @@ impl Engine {
// Find the variable in the scope
scope
.get_index(name)
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(name.into(), *pos))?
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(name.to_string(), *pos))?
.0
};
@ -1000,7 +1003,7 @@ impl Engine {
match rhs {
// xxx.fn_name(arg_expr_list)
Expr::FnCall(x, pos) if x.namespace.is_none() => {
let FnCallInfo {
let FnCallExpr {
name,
native_only: native,
hash,
@ -1073,7 +1076,7 @@ impl Engine {
}
// {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() => {
let FnCallInfo {
let FnCallExpr {
name,
native_only: native,
hash,
@ -1157,7 +1160,7 @@ impl Engine {
}
// xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr
Expr::FnCall(f, pos) if f.namespace.is_none() => {
let FnCallInfo {
let FnCallExpr {
name,
native_only: native,
hash,
@ -1209,14 +1212,7 @@ impl Engine {
level: usize,
new_val: Option<(Dynamic, Position)>,
) -> Result<Dynamic, Box<EvalAltResult>> {
let (
BinaryExpr {
lhs: dot_lhs,
rhs: dot_rhs,
},
chain_type,
op_pos,
) = match expr {
let (BinaryExpr { lhs, rhs }, chain_type, op_pos) = match expr {
Expr::Index(x, pos) => (x.as_ref(), ChainType::Index, *pos),
Expr::Dot(x, pos) => (x.as_ref(), ChainType::Dot, *pos),
_ => unreachable!(),
@ -1230,17 +1226,17 @@ impl Engine {
state,
lib,
this_ptr,
dot_rhs,
rhs,
chain_type,
&mut idx_values,
0,
level,
)?;
match dot_lhs {
match lhs {
// id.??? or id[???]
Expr::Variable(x) => {
let Ident {
let IdentX {
name: var_name,
pos: var_pos,
} = &x.3;
@ -1249,7 +1245,7 @@ impl Engine {
.map_err(|err| err.fill_position(*var_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
match typ {
@ -1262,7 +1258,7 @@ impl Engine {
let obj_ptr = &mut target.into();
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,
)
.map(|(v, _)| v)
@ -1275,7 +1271,7 @@ impl Engine {
let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
let obj_ptr = &mut val.into();
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,
)
.map(|(v, _)| v)
@ -1549,12 +1545,10 @@ impl Engine {
Expr::IntegerConstant(x, _) => Ok((*x).into()),
#[cfg(not(feature = "no_float"))]
Expr::FloatConstant(x, _) => Ok(x.0.into()),
Expr::StringConstant(x) => Ok(x.name.clone().into()),
Expr::FloatConstant(x, _) => Ok((*x).into()),
Expr::StringConstant(x, _) => Ok(x.clone().into()),
Expr::CharConstant(x, _) => Ok((*x).into()),
Expr::FnPointer(x) => {
Ok(FnPtr::new_unchecked(x.name.clone(), Default::default()).into())
}
Expr::FnPointer(x, _) => Ok(FnPtr::new_unchecked(x.clone(), Default::default()).into()),
Expr::Variable(x) if (x.3).name == KEYWORD_THIS => {
if let Some(val) = this_ptr {
Ok(val.clone())
@ -1605,7 +1599,7 @@ impl Engine {
// Normal function call
Expr::FnCall(x, pos) if x.namespace.is_none() => {
let FnCallInfo {
let FnCallExpr {
name,
native_only: native,
capture: cap_scope,
@ -1622,9 +1616,9 @@ impl Engine {
.map_err(|err| err.fill_position(*pos))
}
// Module-qualified function call
// Namespace-qualified function call
Expr::FnCall(x, pos) if x.namespace.is_some() => {
let FnCallInfo {
let FnCallExpr {
name,
namespace,
hash,
@ -1632,9 +1626,9 @@ impl Engine {
def_value,
..
} = 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(
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,
)
.map_err(|err| err.fill_position(*pos))
@ -1674,6 +1668,25 @@ impl Engine {
Expr::False(_) => Ok(false.into()),
Expr::Unit(_) => Ok(().into()),
Expr::Switch(x, _) => {
let (match_expr, table, def_stmt) = x.as_ref();
let match_item =
self.eval_expr(scope, mods, state, lib, this_ptr, match_expr, level)?;
let hasher = &mut get_hasher();
match_item.hash(hasher);
let hash = hasher.finish();
if let Some(stmt) = table.get(&hash) {
self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level)
} else if let Some(def_stmt) = def_stmt {
self.eval_stmt(scope, mods, state, lib, this_ptr, def_stmt, level)
} else {
Ok(().into())
}
}
Expr::Custom(custom, _) => {
let expressions = custom
.keywords()
@ -2184,13 +2197,14 @@ impl Engine {
// Export statement
#[cfg(not(feature = "no_module"))]
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
if let Some(index) = scope.get_index(name).map(|(i, _)| i) {
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 {
return EvalAltResult::ErrorVariableNotFound(name.into(), *id_pos).into();
return EvalAltResult::ErrorVariableNotFound(name.to_string(), *id_pos)
.into();
}
}
Ok(Default::default())

View File

@ -9,6 +9,7 @@ use crate::parse_error::ParseError;
use crate::result::EvalAltResult;
use crate::scope::Scope;
use crate::token::{Position, NO_POS};
use crate::utils::get_hasher;
#[cfg(not(feature = "no_index"))]
use crate::{
@ -35,6 +36,7 @@ use crate::optimize::optimize_into_ast;
use crate::stdlib::{
any::{type_name, TypeId},
boxed::Box,
hash::{Hash, Hasher},
string::String,
};
@ -45,6 +47,13 @@ use crate::stdlib::mem;
#[cfg(not(target_arch = "wasm32"))]
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
impl Engine {
/// Register a function of the `Engine`.
@ -907,8 +916,9 @@ impl Engine {
scripts: &[&str],
optimization_level: OptimizationLevel,
) -> Result<AST, ParseError> {
let hash = calc_hash_for_scripts(scripts);
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.
@ -1061,6 +1071,7 @@ impl Engine {
.into());
};
let hash = calc_hash_for_scripts(&scripts);
let stream = self.lex(
&scripts,
if has_null {
@ -1073,8 +1084,12 @@ impl Engine {
None
},
);
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,
)?;
// Handle null - map to ()
if has_null {
@ -1155,11 +1170,11 @@ impl Engine {
script: &str,
) -> Result<AST, ParseError> {
let scripts = [script];
let hash = calc_hash_for_scripts(&scripts);
let stream = self.lex(&scripts, None);
{
let mut peekable = stream.peekable();
self.parse_global_expr(&mut peekable, scope, self.optimization_level)
}
let mut peekable = stream.peekable();
self.parse_global_expr(hash, &mut peekable, scope, self.optimization_level)
}
/// Evaluate a script file.
@ -1316,10 +1331,12 @@ impl Engine {
script: &str,
) -> Result<T, Box<EvalAltResult>> {
let scripts = [script];
let hash = calc_hash_for_scripts(&scripts);
let stream = self.lex(&scripts, None);
// 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)
}
@ -1446,8 +1463,9 @@ impl Engine {
script: &str,
) -> Result<(), Box<EvalAltResult>> {
let scripts = [script];
let hash = calc_hash_for_scripts(&scripts);
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)
}

View File

@ -53,7 +53,7 @@ impl Engine {
#[cfg(not(feature = "unchecked"))]
#[inline(always)]
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
}
@ -61,7 +61,7 @@ impl Engine {
#[cfg(not(feature = "unchecked"))]
#[inline(always)]
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
@ -69,7 +69,7 @@ impl Engine {
#[cfg(not(feature = "unchecked"))]
#[inline(always)]
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
} else {
operations
@ -81,7 +81,7 @@ impl Engine {
#[cfg(not(feature = "unchecked"))]
#[inline(always)]
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.
@ -89,7 +89,7 @@ impl Engine {
#[cfg(not(feature = "no_module"))]
#[inline(always)]
pub fn set_max_modules(&mut self, modules: usize) -> &mut Self {
self.limits_set.max_modules = modules;
self.limits.max_modules = modules;
self
}
@ -98,7 +98,7 @@ impl Engine {
#[cfg(not(feature = "no_module"))]
#[inline(always)]
pub fn max_modules(&self) -> usize {
self.limits_set.max_modules
self.limits.max_modules
}
/// Set the depth limits for expressions (0 for unlimited).
@ -109,14 +109,14 @@ impl Engine {
max_expr_depth: usize,
#[cfg(not(feature = "no_function"))] max_function_expr_depth: usize,
) -> &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
} else {
max_expr_depth
};
#[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
} else {
max_function_expr_depth
@ -129,7 +129,7 @@ impl Engine {
#[cfg(not(feature = "unchecked"))]
#[inline(always)]
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).
@ -137,14 +137,14 @@ impl Engine {
#[cfg(not(feature = "no_function"))]
#[inline(always)]
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).
#[cfg(not(feature = "unchecked"))]
#[inline(always)]
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
}
@ -152,7 +152,7 @@ impl Engine {
#[cfg(not(feature = "unchecked"))]
#[inline(always)]
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).
@ -160,7 +160,7 @@ impl Engine {
#[cfg(not(feature = "no_index"))]
#[inline(always)]
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
}
@ -169,7 +169,7 @@ impl Engine {
#[cfg(not(feature = "no_index"))]
#[inline(always)]
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).
@ -177,7 +177,7 @@ impl Engine {
#[cfg(not(feature = "no_object"))]
#[inline(always)]
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
}
@ -186,7 +186,7 @@ impl Engine {
#[cfg(not(feature = "no_object"))]
#[inline(always)]
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`.

View File

@ -8,7 +8,7 @@ use crate::engine::{
KEYWORD_PRINT, KEYWORD_TYPE_OF,
};
use crate::fn_native::{FnCallArgs, FnPtr};
use crate::module::{Module, ModuleRef};
use crate::module::{Module, NamespaceRef};
use crate::optimize::OptimizationLevel;
use crate::parse_error::ParseErrorType;
use crate::result::EvalAltResult;
@ -432,13 +432,13 @@ impl Engine {
pub(crate) fn has_override_by_name_and_arguments(
&self,
lib: &[&Module],
name: &str,
fn_name: &str,
arg_types: impl AsRef<[TypeId]>,
pub_only: bool,
) -> bool {
let arg_types = arg_types.as_ref();
let hash_fn = calc_native_fn_hash(empty(), name, arg_types.iter().cloned());
let hash_script = calc_script_fn_hash(empty(), name, arg_types.len());
let hash_fn = calc_native_fn_hash(empty(), fn_name, arg_types.iter().cloned());
let hash_script = calc_script_fn_hash(empty(), fn_name, arg_types.len());
self.has_override(lib, hash_fn, hash_script, pub_only)
}
@ -694,7 +694,7 @@ impl Engine {
mods: &mut Imports,
state: &mut State,
lib: &[&Module],
name: &str,
fn_name: &str,
hash_script: u64,
target: &mut Target,
mut call_args: StaticVec<Dynamic>,
@ -707,9 +707,9 @@ impl Engine {
// Get a reference to the mutation target Dynamic
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
let fn_ptr = obj.read_lock::<FnPtr>().unwrap();
// Redirect function name
@ -733,7 +733,7 @@ impl Engine {
self.exec_fn_call(
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[0].is::<FnPtr>()
{
@ -760,7 +760,7 @@ impl Engine {
self.exec_fn_call(
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
let fn_ptr = obj.read_lock::<FnPtr>().unwrap();
Ok((
@ -779,7 +779,7 @@ impl Engine {
} else if {
#[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")]
false
@ -793,11 +793,11 @@ impl Engine {
// Check if it is a map method call in OOP style
#[cfg(not(feature = "no_object"))]
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>() {
// Remap the function name
_redirected = fn_ptr.get_fn_name().clone();
_fn_name = &_redirected;
fn_name = &_redirected;
// Add curried arguments
fn_ptr
.curry()
@ -809,7 +809,7 @@ impl Engine {
hash = if native {
0
} 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();
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,
)
}?;
@ -848,7 +847,7 @@ impl Engine {
state: &mut State,
lib: &[&Module],
this_ptr: &mut Option<&mut Dynamic>,
name: &str,
fn_name: &str,
args_expr: impl AsRef<[Expr]>,
def_val: Option<Dynamic>,
mut hash_script: u64,
@ -860,8 +859,9 @@ impl Engine {
let args_expr = args_expr.as_ref();
// Handle Fn()
if name == KEYWORD_FN_PTR && args_expr.len() == 1 {
let hash_fn = calc_native_fn_hash(empty(), name, once(TypeId::of::<ImmutableString>()));
if fn_name == KEYWORD_FN_PTR && args_expr.len() == 1 {
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) {
// Fn - only in function call style
@ -878,7 +878,7 @@ impl Engine {
}
// 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)?;
if !fn_ptr.is::<FnPtr>() {
@ -905,7 +905,7 @@ impl Engine {
// Handle is_shared()
#[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)?;
return Ok(value.is_shared().into());
@ -915,7 +915,7 @@ impl Engine {
let redirected;
let mut args_expr = args_expr.as_ref();
let mut curry = StaticVec::new();
let mut name = name;
let mut name = fn_name;
if name == KEYWORD_FN_PTR_CALL
&& args_expr.len() >= 1
@ -1078,7 +1078,7 @@ impl Engine {
.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.
pub(crate) fn make_qualified_function_call(
&self,
@ -1087,8 +1087,8 @@ impl Engine {
state: &mut State,
lib: &[&Module],
this_ptr: &mut Option<&mut Dynamic>,
modules: Option<&ModuleRef>,
name: &str,
namespace: Option<&NamespaceRef>,
fn_name: &str,
args_expr: impl AsRef<[Expr]>,
def_val: Option<bool>,
hash_script: u64,
@ -1096,7 +1096,7 @@ impl Engine {
) -> Result<Dynamic, Box<EvalAltResult>> {
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 first_arg_value = None;
let mut args: StaticVec<_>;
@ -1105,7 +1105,7 @@ impl Engine {
// No arguments
args = Default::default();
} 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
// &mut first argument and avoid cloning the value
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)
let func = match module.get_qualified_fn(hash_script) {
@ -1159,7 +1159,7 @@ impl Engine {
None => {
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,
// i.e. qualifiers + function name + number of arguments.
// 2) Calculate a second hash with no qualifiers, empty function name,
@ -1210,8 +1210,8 @@ impl Engine {
None => EvalAltResult::ErrorFunctionNotFound(
format!(
"{}{} ({})",
modules,
name,
namespace,
fn_name,
args.iter()
.map(|a| if a.is::<ImmutableString>() {
"&str | ImmutableString | String"

View File

@ -83,19 +83,20 @@ mod token;
mod r#unsafe;
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.
#[cfg(not(feature = "only_i32"))]
pub type INT = i64;
/// 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.
#[cfg(feature = "only_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.
#[cfg(not(feature = "no_float"))]
@ -103,6 +104,7 @@ pub type INT = i32;
pub type FLOAT = f64;
/// The system floating-point type.
/// It is defined as `f32` since the `f32_float` feature is used.
///
/// Not available under the `no_float` feature.
#[cfg(not(feature = "no_float"))]
@ -168,8 +170,7 @@ pub use token::{get_next_token, parse_string_literal, InputStream, Token, Tokeni
#[cfg(feature = "internals")]
#[deprecated(note = "this type is volatile and may change")]
pub use ast::{
BinaryExpr, CustomExpr, Expr, FloatWrapper, FnCallInfo, Ident, IdentX, ReturnType, ScriptFnDef,
Stmt,
BinaryExpr, CustomExpr, Expr, FnCallExpr, Ident, IdentX, ReturnType, ScriptFnDef, Stmt,
};
#[cfg(feature = "internals")]
@ -178,7 +179,7 @@ pub use engine::{Imports, Limits, State as EvalState};
#[cfg(feature = "internals")]
#[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),
/// which is a specialized `Vec` backed by a small, fixed-size array when there are <= 4 items stored.

View File

@ -1,6 +1,6 @@
//! Module defining external-loaded modules for Rhai.
use crate::ast::{FnAccess, Ident};
use crate::ast::{FnAccess, IdentX};
use crate::dynamic::{Dynamic, Variant};
use crate::fn_native::{
shared_make_mut, shared_take_or_clone, CallableFunction, FnCallArgs, IteratorFn,
@ -55,8 +55,8 @@ pub struct FuncInfo {
pub types: Option<StaticVec<TypeId>>,
}
/// An imported module, which may contain variables, sub-modules,
/// external Rust functions, and script-defined functions.
/// A module which may contain variables, sub-modules, external Rust functions,
/// and/or script-defined functions.
///
/// Not available under the `no_module` feature.
#[derive(Default, Clone)]
@ -253,7 +253,7 @@ impl Module {
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.
///
/// 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.
///
/// 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 {
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,
// i.e. qualifiers + function name + number of arguments.
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.
///
/// 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.
///
/// ## WARNING
///
/// This type is volatile and may change.
#[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 {
fmt::Debug::fmt(&self.0, f)?;
@ -1542,31 +1542,31 @@ impl fmt::Debug for ModuleRef {
}
}
impl Deref for ModuleRef {
type Target = StaticVec<Ident>;
impl Deref for NamespaceRef {
type Target = StaticVec<IdentX>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for ModuleRef {
impl DerefMut for NamespaceRef {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl fmt::Display for ModuleRef {
impl fmt::Display for NamespaceRef {
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())?;
}
Ok(())
}
}
impl From<StaticVec<Ident>> for ModuleRef {
fn from(modules: StaticVec<Ident>) -> Self {
impl From<StaticVec<IdentX>> for NamespaceRef {
fn from(modules: StaticVec<IdentX>) -> Self {
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> {
self.1
}

File diff suppressed because it is too large Load Diff

View File

@ -281,7 +281,7 @@ mod array_functions {
})
.map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall(
"filter".to_string(),
"index_of".to_string(),
err,
NO_POS,
))
@ -314,7 +314,7 @@ mod array_functions {
})
.map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall(
"filter".to_string(),
"some".to_string(),
err,
NO_POS,
))
@ -347,7 +347,7 @@ mod array_functions {
})
.map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall(
"filter".to_string(),
"all".to_string(),
err,
NO_POS,
))
@ -449,7 +449,7 @@ mod array_functions {
})
.map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall(
"reduce".to_string(),
"reduce_rev".to_string(),
err,
NO_POS,
))
@ -467,7 +467,7 @@ mod array_functions {
) -> Result<Dynamic, Box<EvalAltResult>> {
let mut result = initial.call_dynamic(ctx, None, []).map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall(
"reduce".to_string(),
"reduce_rev".to_string(),
err,
NO_POS,
))
@ -486,7 +486,7 @@ mod array_functions {
})
.map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall(
"reduce".to_string(),
"reduce_rev".to_string(),
err,
NO_POS,
))
@ -556,7 +556,7 @@ mod array_functions {
})
.map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall(
"filter".to_string(),
"drain".to_string(),
err,
NO_POS,
))
@ -615,7 +615,7 @@ mod array_functions {
})
.map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall(
"filter".to_string(),
"retain".to_string(),
err,
NO_POS,
))

View File

@ -108,6 +108,8 @@ pub enum ParseErrorType {
///
/// Never appears under the `no_object` feature.
DuplicatedProperty(String),
/// A switch case is duplicated.
DuplicatedSwitchCase,
/// Missing a property name for custom types and maps.
///
/// Never appears under the `no_object` feature.
@ -177,6 +179,7 @@ impl ParseErrorType {
Self::MalformedInExpr(_) => "Invalid 'in' expression",
Self::MalformedCapture(_) => "Invalid capturing",
Self::DuplicatedProperty(_) => "Duplicated property in object map literal",
Self::DuplicatedSwitchCase => "Duplicated switch case",
Self::PropertyExpected => "Expecting name of a property",
Self::VariableExpected => "Expecting name of a variable",
Self::Reserved(_) => "Invalid use of reserved keyword",
@ -211,6 +214,7 @@ impl fmt::Display for ParseErrorType {
Self::DuplicatedProperty(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),

File diff suppressed because it is too large Load Diff

View File

@ -34,7 +34,7 @@ pub type FnCustomSyntaxParse =
dyn Fn(&[String]) -> Result<Option<String>, ParseError> + Send + Sync;
/// An expression sub-tree in an AST.
#[derive(Debug, Clone, Hash)]
#[derive(Debug, Clone)]
pub struct Expression<'a>(&'a Expr);
impl<'a> From<&'a Expr> for Expression<'a> {

View File

@ -216,6 +216,10 @@ pub enum Token {
Colon,
/// `::`
DoubleColon,
/// `=>`
DoubleArrow,
/// `_`
Underscore,
/// `,`
Comma,
/// `.`
@ -236,6 +240,8 @@ pub enum Token {
If,
/// `else`
Else,
/// `switch`
Switch,
/// `while`
While,
/// `loop`
@ -371,6 +377,8 @@ impl Token {
SemiColon => ";",
Colon => ":",
DoubleColon => "::",
DoubleArrow => "=>",
Underscore => "_",
Comma => ",",
Period => ".",
MapStart => "#{",
@ -381,6 +389,7 @@ impl Token {
Const => "const",
If => "if",
Else => "else",
Switch => "switch",
While => "while",
Loop => "loop",
For => "for",
@ -455,6 +464,8 @@ impl Token {
";" => SemiColon,
":" => Colon,
"::" => DoubleColon,
"=>" => DoubleArrow,
"_" => Underscore,
"," => Comma,
"." => Period,
"#{" => MapStart,
@ -465,6 +476,7 @@ impl Token {
"const" => Const,
"if" => If,
"else" => Else,
"switch" => Switch,
"while" => While,
"loop" => Loop,
"for" => For,
@ -521,11 +533,12 @@ impl Token {
#[cfg(feature = "no_module")]
"import" | "export" | "as" => Reserved(syntax.into()),
"===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "public"
| "new" | "use" | "module" | "package" | "var" | "static" | "shared" | "with"
| "do" | "each" | "then" | "goto" | "exit" | "switch" | "match" | "case"
| "default" | "void" | "null" | "nil" | "spawn" | "thread" | "go" | "sync"
| "async" | "await" | "yield" => Reserved(syntax.into()),
"===" | "!==" | "->" | "<-" | ":=" | "::<" | "(*" | "*)" | "#" | "public" | "new"
| "use" | "module" | "package" | "var" | "static" | "shared" | "with" | "do"
| "each" | "then" | "goto" | "exit" | "match" | "case" | "default" | "void"
| "null" | "nil" | "spawn" | "thread" | "go" | "sync" | "async" | "await" | "yield" => {
Reserved(syntax.into())
}
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
| 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);
return Some((Token::Reserved("=>".into()), start_pos));
return Some((Token::DoubleArrow, 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 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 {
return Some((
@ -1490,10 +1507,7 @@ fn get_identifier(
));
}
return Some((
Token::lookup_from_syntax(&identifier).unwrap_or_else(|| Token::Identifier(identifier)),
start_pos,
));
return Some((Token::Identifier(identifier), start_pos));
}
/// Is this keyword allowed as a function?
@ -1654,9 +1668,6 @@ impl<'a> Iterator for TokenIterator<'a, '_> {
("<-", false) => Token::LexError(LERR::ImproperSymbol(
"'<-' 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(
"':=' is not a valid assignment operator. This is not Go! Should it be simply '='?".to_string(),
)),
@ -1735,7 +1746,7 @@ impl Engine {
engine: self,
state: TokenizeState {
#[cfg(not(feature = "unchecked"))]
max_string_size: self.limits_set.max_string_size,
max_string_size: self.limits.max_string_size,
#[cfg(feature = "unchecked")]
max_string_size: 0,
non_unary: false,

View File

@ -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.
///
/// 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)
}
/// _[INTERNALS]_ Calculate a `u64` hash key from a module-qualified function name and the number of parameters,
/// but no parameter types.
/// _[INTERNALS]_ Calculate a `u64` hash key from a namespace-qualified function name
/// and the number of parameters, but no parameter types.
/// Exported under the `internals` feature only.
///
/// 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())
}
/// 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.
/// Parameter types are passed in via `TypeId` values from an iterator.
@ -106,10 +116,7 @@ fn calc_fn_hash<'a>(
num: Option<usize>,
params: impl Iterator<Item = TypeId>,
) -> u64 {
#[cfg(feature = "no_std")]
let s: &mut AHasher = &mut Default::default();
#[cfg(not(feature = "no_std"))]
let s = &mut DefaultHasher::new();
let s = &mut get_hasher();
// We always skip the first module
modules.skip(1).for_each(|m| m.hash(s));

View File

@ -40,9 +40,8 @@ fn test_bool_op_short_circuit() -> Result<(), Box<EvalAltResult>> {
engine.eval::<bool>(
r"
let x = true;
x || { throw; };
"
"
)?,
true
);
@ -51,9 +50,8 @@ fn test_bool_op_short_circuit() -> Result<(), Box<EvalAltResult>> {
engine.eval::<bool>(
r"
let x = false;
x && { throw; };
"
"
)?,
false
);
@ -68,10 +66,9 @@ fn test_bool_op_no_short_circuit1() {
assert!(engine
.eval::<bool>(
r"
let x = true;
x | { throw; }
"
let x = true;
x | { throw; }
"
)
.is_err());
}
@ -83,10 +80,9 @@ fn test_bool_op_no_short_circuit2() {
assert!(engine
.eval::<bool>(
r"
let x = false;
x & { throw; }
"
let x = false;
x & { throw; }
"
)
.is_err());
}

View File

@ -73,7 +73,8 @@ fn test_fn_ptr() -> Result<(), Box<EvalAltResult>> {
"#
)
.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(())

64
tests/switch.rs Normal file
View 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(())
}