Merge pull request #708 from schungx/master
Speed improvement attempts.
This commit is contained in:
commit
07d5886100
44
.github/workflows/build.yml
vendored
44
.github/workflows/build.yml
vendored
@ -43,28 +43,28 @@ jobs:
|
|||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
flags:
|
flags:
|
||||||
- ""
|
- ""
|
||||||
- "--features debugging"
|
- "--features testing-environ,debugging"
|
||||||
- "--features metadata,serde,internals"
|
- "--features testing-environ,metadata,serde,internals"
|
||||||
- "--features unchecked,serde,metadata,internals,debugging"
|
- "--features testing-environ,unchecked,serde,metadata,internals,debugging"
|
||||||
- "--features sync,serde,metadata,internals,debugging"
|
- "--features testing-environ,sync,serde,metadata,internals,debugging"
|
||||||
- "--features no_position,serde,metadata,internals,debugging"
|
- "--features testing-environ,no_position,serde,metadata,internals,debugging"
|
||||||
- "--features no_optimize,serde,metadata,internals,debugging"
|
- "--features testing-environ,no_optimize,serde,metadata,internals,debugging"
|
||||||
- "--features no_float,serde,metadata,internals,debugging"
|
- "--features testing-environ,no_float,serde,metadata,internals,debugging"
|
||||||
- "--features f32_float,serde,metadata,internals,debugging"
|
- "--features testing-environ,f32_float,serde,metadata,internals,debugging"
|
||||||
- "--features decimal,serde,metadata,internals,debugging"
|
- "--features testing-environ,decimal,serde,metadata,internals,debugging"
|
||||||
- "--features no_custom_syntax,serde,metadata,internals,debugging"
|
- "--features testing-environ,no_custom_syntax,serde,metadata,internals,debugging"
|
||||||
- "--features no_float,decimal"
|
- "--features testing-environ,no_float,decimal"
|
||||||
- "--tests --features only_i32,serde,metadata,internals,debugging"
|
- "--tests --features testing-environ,only_i32,serde,metadata,internals,debugging"
|
||||||
- "--features only_i64,serde,metadata,internals,debugging"
|
- "--features testing-environ,only_i64,serde,metadata,internals,debugging"
|
||||||
- "--features no_index,serde,metadata,internals,debugging"
|
- "--features testing-environ,no_index,serde,metadata,internals,debugging"
|
||||||
- "--features no_object,serde,metadata,internals,debugging"
|
- "--features testing-environ,no_object,serde,metadata,internals,debugging"
|
||||||
- "--features no_function,serde,metadata,internals,debugging"
|
- "--features testing-environ,no_function,serde,metadata,internals,debugging"
|
||||||
- "--features no_module,serde,metadata,internals,debugging"
|
- "--features testing-environ,no_module,serde,metadata,internals,debugging"
|
||||||
- "--features no_time,serde,metadata,internals,debugging"
|
- "--features testing-environ,no_time,serde,metadata,internals,debugging"
|
||||||
- "--features no_closure,serde,metadata,internals,debugging"
|
- "--features testing-environ,no_closure,serde,metadata,internals,debugging"
|
||||||
- "--features unicode-xid-ident,serde,metadata,internals,debugging"
|
- "--features testing-environ,unicode-xid-ident,serde,metadata,internals,debugging"
|
||||||
- "--features sync,no_time,no_function,no_float,no_position,no_optimize,no_module,no_closure,no_custom_syntax,metadata,serde,unchecked,debugging"
|
- "--features testing-environ,sync,no_time,no_function,no_float,no_position,no_optimize,no_module,no_closure,no_custom_syntax,metadata,serde,unchecked,debugging"
|
||||||
- "--features no_time,no_function,no_float,no_position,no_index,no_object,no_optimize,no_module,no_closure,no_custom_syntax,unchecked"
|
- "--features testing-environ,no_time,no_function,no_float,no_position,no_index,no_object,no_optimize,no_module,no_closure,no_custom_syntax,unchecked"
|
||||||
toolchain: [stable]
|
toolchain: [stable]
|
||||||
experimental: [false]
|
experimental: [false]
|
||||||
include:
|
include:
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,6 +6,7 @@ benches/results
|
|||||||
clippy.toml
|
clippy.toml
|
||||||
Rhai.toml
|
Rhai.toml
|
||||||
**/*.bat
|
**/*.bat
|
||||||
|
**/*.exe
|
||||||
doc/rhai-sync.json
|
doc/rhai-sync.json
|
||||||
doc/rhai.json
|
doc/rhai.json
|
||||||
.idea/
|
.idea/
|
||||||
|
15
CHANGELOG.md
15
CHANGELOG.md
@ -12,11 +12,26 @@ Bug fixes
|
|||||||
* Syntax such as `foo.bar::baz` no longer panics, but returns a proper parse error.
|
* Syntax such as `foo.bar::baz` no longer panics, but returns a proper parse error.
|
||||||
* `x += y` where `x` and `y` are `char` now works correctly.
|
* `x += y` where `x` and `y` are `char` now works correctly.
|
||||||
* Expressions such as `!inside` now parses correctly instead of as `!in` followed by `side`.
|
* Expressions such as `!inside` now parses correctly instead of as `!in` followed by `side`.
|
||||||
|
* Custom syntax starting with symbols now works correctly and no longer raises a parse error.
|
||||||
|
* Comparing different custom types now works correctly when the appropriate comparison operators are registered.
|
||||||
|
* Op-assignments to bit flags or bit ranges now work correctly.
|
||||||
|
|
||||||
|
Potentially breaking changes
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
* The trait method `ModuleResolver::resolve_raw` (which is a low-level API) now takes a `&mut Scope` parameter. This is a breaking change because the signature is modified, but this trait method has a default and is rarely called/implemented in practice.
|
||||||
|
* `Module::eval_ast_as_new_raw` (a low-level API) now takes a `&mut Scope` instead of the `Scope` parameter. This is a breaking change because the `&mut` is now required.
|
||||||
|
* `Engine::allow_loop_expressions` now correctly defaults to `true` (was erroneously `false` by default).
|
||||||
|
|
||||||
Enhancements
|
Enhancements
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
* `Engine::new_raw` is now `const` and runs very fast, delaying all other initialization until first use.
|
||||||
* The functions `min` and `max` are added for numbers.
|
* The functions `min` and `max` are added for numbers.
|
||||||
|
* Range cases in `switch` statements now also match floating-point and decimal values. In order to support this, however, small numeric ranges cases are no longer unrolled.
|
||||||
|
* Loading a module via `import` now gives the module access to the current scope, including variables and constants defined inside.
|
||||||
|
* Some very simple operator calls (e.g. integer add) are short-circuited to avoid the overhead of a function call, resulting in a small speed improvement.
|
||||||
|
* The tokenizer now uses table-driven keyword recognizers generated by GNU gperf. At least _theoretically_ it should be faster...
|
||||||
|
|
||||||
|
|
||||||
Version 1.12.0
|
Version 1.12.0
|
||||||
|
@ -34,7 +34,7 @@ serde_json = { version = "1.0", default-features = false, features = ["alloc"],
|
|||||||
unicode-xid = { version = "0.2", default-features = false, optional = true }
|
unicode-xid = { version = "0.2", default-features = false, optional = true }
|
||||||
rust_decimal = { version = "1.16", default-features = false, features = ["maths"], optional = true }
|
rust_decimal = { version = "1.16", default-features = false, features = ["maths"], optional = true }
|
||||||
getrandom = { version = "0.2", optional = true }
|
getrandom = { version = "0.2", optional = true }
|
||||||
rustyline = { version = "10", optional = true }
|
rustyline = { version = "11", optional = true }
|
||||||
document-features = { version = "0.2", optional = true }
|
document-features = { version = "0.2", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
@ -117,6 +117,11 @@ std = ["ahash/std", "num-traits/std", "smartstring/std"]
|
|||||||
## Use [`stdweb`](https://crates.io/crates/stdweb) as JavaScript interface.
|
## Use [`stdweb`](https://crates.io/crates/stdweb) as JavaScript interface.
|
||||||
stdweb = ["getrandom/js", "instant/stdweb"]
|
stdweb = ["getrandom/js", "instant/stdweb"]
|
||||||
|
|
||||||
|
#! ### Features used in testing environments only
|
||||||
|
|
||||||
|
## Running under a testing environment.
|
||||||
|
testing-environ = []
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "rhai-repl"
|
name = "rhai-repl"
|
||||||
required-features = ["rustyline"]
|
required-features = ["rustyline"]
|
||||||
@ -151,4 +156,4 @@ features = ["document-features", "metadata", "serde", "internals", "decimal", "d
|
|||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
# Notice that a custom modified version of `rustyline` is used which supports bracketed paste on Windows.
|
# Notice that a custom modified version of `rustyline` is used which supports bracketed paste on Windows.
|
||||||
# This can be moved to the official version when bracketed paste is added.
|
# This can be moved to the official version when bracketed paste is added.
|
||||||
rustyline = { git = "https://github.com/schungx/rustyline", branch = "v10" }
|
rustyline = { git = "https://github.com/schungx/rustyline", branch = "v11" }
|
||||||
|
@ -8,7 +8,7 @@ note: required by a bound in `rhai::Dynamic::cast`
|
|||||||
--> $WORKSPACE/src/types/dynamic.rs
|
--> $WORKSPACE/src/types/dynamic.rs
|
||||||
|
|
|
|
||||||
| pub fn cast<T: Any + Clone>(self) -> T {
|
| pub fn cast<T: Any + Clone>(self) -> T {
|
||||||
| ^^^^^ required by this bound in `rhai::Dynamic::cast`
|
| ^^^^^ required by this bound in `Dynamic::cast`
|
||||||
help: consider annotating `NonClonable` with `#[derive(Clone)]`
|
help: consider annotating `NonClonable` with `#[derive(Clone)]`
|
||||||
|
|
|
|
||||||
3 | #[derive(Clone)]
|
3 | #[derive(Clone)]
|
||||||
|
@ -8,7 +8,7 @@ note: required by a bound in `rhai::Dynamic::cast`
|
|||||||
--> $WORKSPACE/src/types/dynamic.rs
|
--> $WORKSPACE/src/types/dynamic.rs
|
||||||
|
|
|
|
||||||
| pub fn cast<T: Any + Clone>(self) -> T {
|
| pub fn cast<T: Any + Clone>(self) -> T {
|
||||||
| ^^^^^ required by this bound in `rhai::Dynamic::cast`
|
| ^^^^^ required by this bound in `Dynamic::cast`
|
||||||
help: consider annotating `NonClonable` with `#[derive(Clone)]`
|
help: consider annotating `NonClonable` with `#[derive(Clone)]`
|
||||||
|
|
|
|
||||||
3 | #[derive(Clone)]
|
3 | #[derive(Clone)]
|
||||||
|
@ -10,7 +10,7 @@ note: required by a bound in `rhai::Dynamic::from`
|
|||||||
--> $WORKSPACE/src/types/dynamic.rs
|
--> $WORKSPACE/src/types/dynamic.rs
|
||||||
|
|
|
|
||||||
| pub fn from<T: Variant + Clone>(value: T) -> Self {
|
| pub fn from<T: Variant + Clone>(value: T) -> Self {
|
||||||
| ^^^^^ required by this bound in `rhai::Dynamic::from`
|
| ^^^^^ required by this bound in `Dynamic::from`
|
||||||
= note: this error originates in the attribute macro `export_fn` (in Nightly builds, run with -Z macro-backtrace for more info)
|
= note: this error originates in the attribute macro `export_fn` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
help: consider annotating `NonClonable` with `#[derive(Clone)]`
|
help: consider annotating `NonClonable` with `#[derive(Clone)]`
|
||||||
|
|
|
|
||||||
|
@ -11,7 +11,7 @@ note: required by a bound in `rhai::Dynamic::from`
|
|||||||
--> $WORKSPACE/src/types/dynamic.rs
|
--> $WORKSPACE/src/types/dynamic.rs
|
||||||
|
|
|
|
||||||
| pub fn from<T: Variant + Clone>(value: T) -> Self {
|
| pub fn from<T: Variant + Clone>(value: T) -> Self {
|
||||||
| ^^^^^ required by this bound in `rhai::Dynamic::from`
|
| ^^^^^ required by this bound in `Dynamic::from`
|
||||||
= note: this error originates in the attribute macro `export_module` (in Nightly builds, run with -Z macro-backtrace for more info)
|
= note: this error originates in the attribute macro `export_module` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
help: consider annotating `NonClonable` with `#[derive(Clone)]`
|
help: consider annotating `NonClonable` with `#[derive(Clone)]`
|
||||||
|
|
|
|
||||||
|
@ -28,4 +28,5 @@ Sub-Directories
|
|||||||
| `func` | Support for function calls |
|
| `func` | Support for function calls |
|
||||||
| `eval` | Evaluation engine |
|
| `eval` | Evaluation engine |
|
||||||
| `serde` | Support for [`serde`](https://crates.io/crates/serde) |
|
| `serde` | Support for [`serde`](https://crates.io/crates/serde) |
|
||||||
|
| `tools` | External tools needed for building |
|
||||||
| `bin` | Pre-built CLI binaries (e.g. `rhai-run`, `rhai-repl`) |
|
| `bin` | Pre-built CLI binaries (e.g. `rhai-run`, `rhai-repl`) |
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use crate::func::native::locked_write;
|
use crate::func::native::locked_write;
|
||||||
use crate::parser::{ParseResult, ParseState};
|
use crate::parser::{ParseResult, ParseState};
|
||||||
|
use crate::types::StringsInterner;
|
||||||
use crate::{Engine, OptimizationLevel, Scope, AST};
|
use crate::{Engine, OptimizationLevel, Scope, AST};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
@ -126,7 +127,7 @@ impl Engine {
|
|||||||
let path = path.clone();
|
let path = path.clone();
|
||||||
|
|
||||||
match self
|
match self
|
||||||
.module_resolver
|
.module_resolver()
|
||||||
.resolve_ast(self, None, &path, crate::Position::NONE)
|
.resolve_ast(self, None, &path, crate::Position::NONE)
|
||||||
{
|
{
|
||||||
Some(Ok(module_ast)) => collect_imports(&module_ast, &resolver, &mut imports),
|
Some(Ok(module_ast)) => collect_imports(&module_ast, &resolver, &mut imports),
|
||||||
@ -135,7 +136,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let module =
|
let module =
|
||||||
self.module_resolver
|
self.module_resolver()
|
||||||
.resolve(self, None, &path, crate::Position::NONE)?;
|
.resolve(self, None, &path, crate::Position::NONE)?;
|
||||||
|
|
||||||
let module = shared_take_or_clone(module);
|
let module = shared_take_or_clone(module);
|
||||||
@ -223,7 +224,17 @@ impl Engine {
|
|||||||
optimization_level: OptimizationLevel,
|
optimization_level: OptimizationLevel,
|
||||||
) -> ParseResult<AST> {
|
) -> ParseResult<AST> {
|
||||||
let (stream, tc) = self.lex_raw(scripts.as_ref(), self.token_mapper.as_deref());
|
let (stream, tc) = self.lex_raw(scripts.as_ref(), self.token_mapper.as_deref());
|
||||||
let interned_strings = &mut *locked_write(&self.interned_strings);
|
|
||||||
|
let mut interner;
|
||||||
|
let mut guard;
|
||||||
|
let interned_strings = if let Some(ref interner) = self.interned_strings {
|
||||||
|
guard = locked_write(interner);
|
||||||
|
&mut *guard
|
||||||
|
} else {
|
||||||
|
interner = StringsInterner::new();
|
||||||
|
&mut interner
|
||||||
|
};
|
||||||
|
|
||||||
let state = &mut ParseState::new(scope, interned_strings, tc);
|
let state = &mut ParseState::new(scope, interned_strings, tc);
|
||||||
let mut _ast = self.parse(stream.peekable(), state, optimization_level)?;
|
let mut _ast = self.parse(stream.peekable(), state, optimization_level)?;
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
@ -294,7 +305,17 @@ impl Engine {
|
|||||||
) -> ParseResult<AST> {
|
) -> ParseResult<AST> {
|
||||||
let scripts = [script];
|
let scripts = [script];
|
||||||
let (stream, t) = self.lex_raw(&scripts, self.token_mapper.as_deref());
|
let (stream, t) = self.lex_raw(&scripts, self.token_mapper.as_deref());
|
||||||
let interned_strings = &mut *locked_write(&self.interned_strings);
|
|
||||||
|
let mut interner;
|
||||||
|
let mut guard;
|
||||||
|
let interned_strings = if let Some(ref interner) = self.interned_strings {
|
||||||
|
guard = locked_write(interner);
|
||||||
|
&mut *guard
|
||||||
|
} else {
|
||||||
|
interner = StringsInterner::new();
|
||||||
|
&mut interner
|
||||||
|
};
|
||||||
|
|
||||||
let state = &mut ParseState::new(Some(scope), interned_strings, t);
|
let state = &mut ParseState::new(Some(scope), interned_strings, t);
|
||||||
self.parse_global_expr(stream.peekable(), state, |_| {}, self.optimization_level)
|
self.parse_global_expr(stream.peekable(), state, |_| {}, self.optimization_level)
|
||||||
}
|
}
|
||||||
|
@ -232,7 +232,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let token = Token::lookup_symbol_from_syntax(s).or_else(|| {
|
let token = Token::lookup_symbol_from_syntax(s).or_else(|| {
|
||||||
if is_reserved_keyword_or_symbol(s) {
|
if is_reserved_keyword_or_symbol(s).0 {
|
||||||
Some(Token::Reserved(Box::new(s.into())))
|
Some(Token::Reserved(Box::new(s.into())))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -255,7 +255,8 @@ impl Engine {
|
|||||||
// Markers not in first position
|
// Markers not in first position
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
CUSTOM_SYNTAX_MARKER_FLOAT if !segments.is_empty() => s.into(),
|
CUSTOM_SYNTAX_MARKER_FLOAT if !segments.is_empty() => s.into(),
|
||||||
// Standard or reserved keyword/symbol not in first position
|
|
||||||
|
// Keyword/symbol not in first position
|
||||||
_ if !segments.is_empty() && token.is_some() => {
|
_ if !segments.is_empty() && token.is_some() => {
|
||||||
// Make it a custom keyword/symbol if it is disabled or reserved
|
// Make it a custom keyword/symbol if it is disabled or reserved
|
||||||
if (self
|
if (self
|
||||||
@ -274,6 +275,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
s.into()
|
s.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Standard keyword in first position but not disabled
|
// Standard keyword in first position but not disabled
|
||||||
_ if segments.is_empty()
|
_ if segments.is_empty()
|
||||||
&& token.as_ref().map_or(false, Token::is_standard_keyword)
|
&& token.as_ref().map_or(false, Token::is_standard_keyword)
|
||||||
@ -291,8 +293,11 @@ impl Engine {
|
|||||||
)
|
)
|
||||||
.into_err(Position::NONE));
|
.into_err(Position::NONE));
|
||||||
}
|
}
|
||||||
// Identifier in first position
|
|
||||||
_ if segments.is_empty() && is_valid_identifier(s) => {
|
// Identifier or symbol in first position
|
||||||
|
_ if segments.is_empty()
|
||||||
|
&& (is_valid_identifier(s) || is_reserved_keyword_or_symbol(s).0) =>
|
||||||
|
{
|
||||||
// Make it a custom keyword/symbol if it is disabled or reserved
|
// Make it a custom keyword/symbol if it is disabled or reserved
|
||||||
if self
|
if self
|
||||||
.disabled_symbols
|
.disabled_symbols
|
||||||
@ -310,6 +315,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
s.into()
|
s.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Anything else is an error
|
// Anything else is an error
|
||||||
_ => {
|
_ => {
|
||||||
return Err(LexError::ImproperSymbol(
|
return Err(LexError::ImproperSymbol(
|
||||||
@ -326,23 +332,20 @@ impl Engine {
|
|||||||
segments.push(seg);
|
segments.push(seg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the syntax has no symbols, just ignore the registration
|
// If the syntax has nothing, just ignore the registration
|
||||||
if segments.is_empty() {
|
if segments.is_empty() {
|
||||||
return Ok(self);
|
return Ok(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The first keyword is the discriminator
|
// The first keyword/symbol is the discriminator
|
||||||
let key = segments[0].clone();
|
let key = segments[0].clone();
|
||||||
|
|
||||||
self.register_custom_syntax_with_state_raw(
|
self.register_custom_syntax_with_state_raw(
|
||||||
key,
|
key,
|
||||||
// Construct the parsing function
|
// Construct the parsing function
|
||||||
move |stream, _, _| {
|
move |stream, _, _| match stream.len() {
|
||||||
if stream.len() >= segments.len() {
|
len if len >= segments.len() => Ok(None),
|
||||||
Ok(None)
|
len => Ok(Some(segments[len].clone())),
|
||||||
} else {
|
|
||||||
Ok(Some(segments[stream.len()].clone()))
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
scope_may_be_changed,
|
scope_may_be_changed,
|
||||||
move |context, expressions, _| func(context, expressions),
|
move |context, expressions, _| func(context, expressions),
|
||||||
@ -399,7 +402,8 @@ impl Engine {
|
|||||||
parse: Box::new(parse),
|
parse: Box::new(parse),
|
||||||
func: Box::new(func),
|
func: Box::new(func),
|
||||||
scope_may_be_changed,
|
scope_may_be_changed,
|
||||||
},
|
}
|
||||||
|
.into(),
|
||||||
);
|
);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ use crate::eval::{Caches, GlobalRuntimeState};
|
|||||||
use crate::func::native::locked_write;
|
use crate::func::native::locked_write;
|
||||||
use crate::parser::ParseState;
|
use crate::parser::ParseState;
|
||||||
use crate::types::dynamic::Variant;
|
use crate::types::dynamic::Variant;
|
||||||
|
use crate::types::StringsInterner;
|
||||||
use crate::{
|
use crate::{
|
||||||
Dynamic, Engine, OptimizationLevel, Position, RhaiResult, RhaiResultOf, Scope, AST, ERR,
|
Dynamic, Engine, OptimizationLevel, Position, RhaiResult, RhaiResultOf, Scope, AST, ERR,
|
||||||
};
|
};
|
||||||
@ -119,7 +120,15 @@ impl Engine {
|
|||||||
) -> RhaiResultOf<T> {
|
) -> RhaiResultOf<T> {
|
||||||
let scripts = [script];
|
let scripts = [script];
|
||||||
let ast = {
|
let ast = {
|
||||||
let interned_strings = &mut *locked_write(&self.interned_strings);
|
let mut interner;
|
||||||
|
let mut guard;
|
||||||
|
let interned_strings = if let Some(ref interner) = self.interned_strings {
|
||||||
|
guard = locked_write(interner);
|
||||||
|
&mut *guard
|
||||||
|
} else {
|
||||||
|
interner = StringsInterner::new();
|
||||||
|
&mut interner
|
||||||
|
};
|
||||||
|
|
||||||
let (stream, tc) = self.lex_raw(&scripts, self.token_mapper.as_deref());
|
let (stream, tc) = self.lex_raw(&scripts, self.token_mapper.as_deref());
|
||||||
|
|
||||||
|
@ -286,7 +286,7 @@ impl Engine {
|
|||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn on_print(&mut self, callback: impl Fn(&str) + SendSync + 'static) -> &mut Self {
|
pub fn on_print(&mut self, callback: impl Fn(&str) + SendSync + 'static) -> &mut Self {
|
||||||
self.print = Box::new(callback);
|
self.print = Some(Box::new(callback));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
/// Override default action of `debug` (print to stdout using [`println!`])
|
/// Override default action of `debug` (print to stdout using [`println!`])
|
||||||
@ -336,7 +336,7 @@ impl Engine {
|
|||||||
&mut self,
|
&mut self,
|
||||||
callback: impl Fn(&str, Option<&str>, Position) + SendSync + 'static,
|
callback: impl Fn(&str, Option<&str>, Position) + SendSync + 'static,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
self.debug = Box::new(callback);
|
self.debug = Some(Box::new(callback));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
/// _(debugging)_ Register a callback for debugging.
|
/// _(debugging)_ Register a callback for debugging.
|
||||||
@ -363,7 +363,7 @@ impl Engine {
|
|||||||
+ SendSync
|
+ SendSync
|
||||||
+ 'static,
|
+ 'static,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
self.debugger_interface = Some(Box::new((Box::new(init), Box::new(callback))));
|
self.debugger_interface = Some((Box::new(init), Box::new(callback)));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
use crate::func::native::locked_write;
|
use crate::func::native::locked_write;
|
||||||
use crate::parser::{ParseSettingFlags, ParseState};
|
use crate::parser::{ParseSettingFlags, ParseState};
|
||||||
use crate::tokenizer::Token;
|
use crate::tokenizer::Token;
|
||||||
|
use crate::types::StringsInterner;
|
||||||
use crate::{Engine, LexError, Map, OptimizationLevel, RhaiResultOf};
|
use crate::{Engine, LexError, Map, OptimizationLevel, RhaiResultOf};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
@ -115,7 +116,16 @@ impl Engine {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let ast = {
|
let ast = {
|
||||||
let interned_strings = &mut *locked_write(&self.interned_strings);
|
let mut interner;
|
||||||
|
let mut guard;
|
||||||
|
let interned_strings = if let Some(ref interner) = self.interned_strings {
|
||||||
|
guard = locked_write(interner);
|
||||||
|
&mut *guard
|
||||||
|
} else {
|
||||||
|
interner = StringsInterner::new();
|
||||||
|
&mut interner
|
||||||
|
};
|
||||||
|
|
||||||
let state = &mut ParseState::new(None, interned_strings, tokenizer_control);
|
let state = &mut ParseState::new(None, interned_strings, tokenizer_control);
|
||||||
|
|
||||||
self.parse_global_expr(
|
self.parse_global_expr(
|
||||||
|
@ -54,7 +54,10 @@ impl Engine {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn module_resolver(&self) -> &dyn crate::ModuleResolver {
|
pub fn module_resolver(&self) -> &dyn crate::ModuleResolver {
|
||||||
&*self.module_resolver
|
static DUMMY_RESOLVER: crate::module::resolvers::DummyModuleResolver =
|
||||||
|
crate::module::resolvers::DummyModuleResolver;
|
||||||
|
|
||||||
|
self.module_resolver.as_deref().unwrap_or(&DUMMY_RESOLVER)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the module resolution service used by the [`Engine`].
|
/// Set the module resolution service used by the [`Engine`].
|
||||||
@ -66,7 +69,7 @@ impl Engine {
|
|||||||
&mut self,
|
&mut self,
|
||||||
resolver: impl crate::ModuleResolver + 'static,
|
resolver: impl crate::ModuleResolver + 'static,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
self.module_resolver = Box::new(resolver);
|
self.module_resolver = Some(Box::new(resolver));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +63,11 @@ impl Engine {
|
|||||||
);
|
);
|
||||||
|
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
_new_ast.set_doc(std::mem::take(ast.doc_mut()));
|
if let Some(doc) = ast.doc_mut() {
|
||||||
|
_new_ast.set_doc(std::mem::take(doc));
|
||||||
|
} else {
|
||||||
|
_new_ast.clear_doc();
|
||||||
|
}
|
||||||
|
|
||||||
_new_ast
|
_new_ast
|
||||||
}
|
}
|
||||||
|
@ -38,23 +38,26 @@ impl LangOptions {
|
|||||||
/// Create a new [`LangOptions`] with default values.
|
/// Create a new [`LangOptions`] with default values.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self::IF_EXPR
|
Self::from_bits_truncate(
|
||||||
| Self::SWITCH_EXPR
|
Self::IF_EXPR.bits()
|
||||||
| Self::STMT_EXPR
|
| Self::SWITCH_EXPR.bits()
|
||||||
| Self::LOOPING
|
| Self::LOOP_EXPR.bits()
|
||||||
| Self::SHADOWING
|
| Self::STMT_EXPR.bits()
|
||||||
| Self::FAST_OPS
|
| Self::LOOPING.bits()
|
||||||
|
| Self::SHADOWING.bits()
|
||||||
|
| Self::FAST_OPS.bits()
|
||||||
| {
|
| {
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
{
|
{
|
||||||
Self::ANON_FN
|
Self::ANON_FN.bits()
|
||||||
}
|
}
|
||||||
#[cfg(feature = "no_function")]
|
#[cfg(feature = "no_function")]
|
||||||
{
|
{
|
||||||
Self::empty()
|
Self::empty().bits()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
//! Module that defines the public function/module registration API of [`Engine`].
|
//! Module that defines the public function/module registration API of [`Engine`].
|
||||||
|
|
||||||
use crate::func::{FnCallArgs, RegisterNativeFunction, SendSync};
|
use crate::func::{FnCallArgs, RegisterNativeFunction, SendSync};
|
||||||
|
use crate::module::ModuleFlags;
|
||||||
use crate::types::dynamic::Variant;
|
use crate::types::dynamic::Variant;
|
||||||
use crate::{
|
use crate::{
|
||||||
Engine, FnAccess, FnNamespace, Identifier, Module, NativeCallContext, RhaiResultOf, Shared,
|
Engine, FnAccess, FnNamespace, Identifier, Module, NativeCallContext, RhaiResultOf, Shared,
|
||||||
@ -14,20 +15,18 @@ use std::prelude::v1::*;
|
|||||||
use crate::func::register::Mut;
|
use crate::func::register::Mut;
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
/// Get the global namespace module (which is the fist module in `global_modules`).
|
|
||||||
#[inline(always)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[must_use]
|
|
||||||
pub(crate) fn global_namespace(&self) -> &Module {
|
|
||||||
self.global_modules.first().unwrap()
|
|
||||||
}
|
|
||||||
/// Get a mutable reference to the global namespace module
|
/// Get a mutable reference to the global namespace module
|
||||||
/// (which is the first module in `global_modules`).
|
/// (which is the first module in `global_modules`).
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn global_namespace_mut(&mut self) -> &mut Module {
|
fn global_namespace_mut(&mut self) -> &mut Module {
|
||||||
let module = self.global_modules.first_mut().unwrap();
|
if self.global_modules.is_empty() {
|
||||||
Shared::get_mut(module).expect("not shared")
|
let mut global_namespace = Module::new();
|
||||||
|
global_namespace.flags |= ModuleFlags::INTERNAL;
|
||||||
|
self.global_modules.push(global_namespace.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Shared::get_mut(self.global_modules.first_mut().unwrap()).expect("not shared")
|
||||||
}
|
}
|
||||||
/// Register a custom function with the [`Engine`].
|
/// Register a custom function with the [`Engine`].
|
||||||
///
|
///
|
||||||
@ -677,6 +676,9 @@ impl Engine {
|
|||||||
/// modules are searched in reverse order.
|
/// modules are searched in reverse order.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn register_global_module(&mut self, module: SharedModule) -> &mut Self {
|
pub fn register_global_module(&mut self, module: SharedModule) -> &mut Self {
|
||||||
|
// Make sure the global namespace is created.
|
||||||
|
let _ = self.global_namespace_mut();
|
||||||
|
|
||||||
// Insert the module into the front.
|
// Insert the module into the front.
|
||||||
// The first module is always the global namespace.
|
// The first module is always the global namespace.
|
||||||
self.global_modules.insert(1, module);
|
self.global_modules.insert(1, module);
|
||||||
@ -729,7 +731,7 @@ impl Engine {
|
|||||||
name: &str,
|
name: &str,
|
||||||
module: SharedModule,
|
module: SharedModule,
|
||||||
) {
|
) {
|
||||||
let separator = crate::tokenizer::Token::DoubleColon.literal_syntax();
|
let separator = crate::engine::NAMESPACE_SEPARATOR;
|
||||||
|
|
||||||
if name.contains(separator) {
|
if name.contains(separator) {
|
||||||
let mut iter = name.splitn(2, separator);
|
let mut iter = name.splitn(2, separator);
|
||||||
@ -779,7 +781,9 @@ impl Engine {
|
|||||||
pub fn gen_fn_signatures(&self, include_packages: bool) -> Vec<String> {
|
pub fn gen_fn_signatures(&self, include_packages: bool) -> Vec<String> {
|
||||||
let mut signatures = Vec::with_capacity(64);
|
let mut signatures = Vec::with_capacity(64);
|
||||||
|
|
||||||
signatures.extend(self.global_namespace().gen_fn_signatures());
|
if let Some(global_namespace) = self.global_modules.first() {
|
||||||
|
signatures.extend(global_namespace.gen_fn_signatures());
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
for (name, m) in self.global_sub_modules.as_deref().into_iter().flatten() {
|
for (name, m) in self.global_sub_modules.as_deref().into_iter().flatten() {
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
use crate::eval::{Caches, GlobalRuntimeState};
|
use crate::eval::{Caches, GlobalRuntimeState};
|
||||||
use crate::func::native::locked_write;
|
use crate::func::native::locked_write;
|
||||||
use crate::parser::ParseState;
|
use crate::parser::ParseState;
|
||||||
|
use crate::types::StringsInterner;
|
||||||
use crate::{Engine, RhaiResultOf, Scope, AST};
|
use crate::{Engine, RhaiResultOf, Scope, AST};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
@ -59,7 +60,17 @@ impl Engine {
|
|||||||
let scripts = [script];
|
let scripts = [script];
|
||||||
let ast = {
|
let ast = {
|
||||||
let (stream, tc) = self.lex_raw(&scripts, self.token_mapper.as_deref());
|
let (stream, tc) = self.lex_raw(&scripts, self.token_mapper.as_deref());
|
||||||
let interned_strings = &mut *locked_write(&self.interned_strings);
|
|
||||||
|
let mut interner;
|
||||||
|
let mut guard;
|
||||||
|
let interned_strings = if let Some(ref interner) = self.interned_strings {
|
||||||
|
guard = locked_write(interner);
|
||||||
|
&mut *guard
|
||||||
|
} else {
|
||||||
|
interner = StringsInterner::new();
|
||||||
|
&mut interner
|
||||||
|
};
|
||||||
|
|
||||||
let state = &mut ParseState::new(Some(scope), interned_strings, tc);
|
let state = &mut ParseState::new(Some(scope), interned_strings, tc);
|
||||||
self.parse(stream.peekable(), state, self.optimization_level)?
|
self.parse(stream.peekable(), state, self.optimization_level)?
|
||||||
};
|
};
|
||||||
|
106
src/ast/ast.rs
106
src/ast/ast.rs
@ -23,9 +23,9 @@ pub struct AST {
|
|||||||
source: Option<ImmutableString>,
|
source: Option<ImmutableString>,
|
||||||
/// [`AST`] documentation.
|
/// [`AST`] documentation.
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
doc: crate::SmartString,
|
doc: Option<Box<crate::SmartString>>,
|
||||||
/// Global statements.
|
/// Global statements.
|
||||||
body: StmtBlock,
|
body: Option<Box<StmtBlock>>,
|
||||||
/// Script-defined functions.
|
/// Script-defined functions.
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
lib: crate::SharedModule,
|
lib: crate::SharedModule,
|
||||||
@ -54,7 +54,14 @@ impl fmt::Debug for AST {
|
|||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
fp.field("resolver", &self.resolver);
|
fp.field("resolver", &self.resolver);
|
||||||
|
|
||||||
fp.field("body", &self.body.as_slice());
|
fp.field(
|
||||||
|
"body",
|
||||||
|
&self
|
||||||
|
.body
|
||||||
|
.as_deref()
|
||||||
|
.map(|b| b.as_slice())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
for (.., fn_def) in self.lib.iter_script_fn() {
|
for (.., fn_def) in self.lib.iter_script_fn() {
|
||||||
@ -75,11 +82,13 @@ impl AST {
|
|||||||
statements: impl IntoIterator<Item = Stmt>,
|
statements: impl IntoIterator<Item = Stmt>,
|
||||||
#[cfg(not(feature = "no_function"))] functions: impl Into<crate::SharedModule>,
|
#[cfg(not(feature = "no_function"))] functions: impl Into<crate::SharedModule>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let stmt = StmtBlock::new(statements, Position::NONE, Position::NONE);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
source: None,
|
source: None,
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
doc: crate::SmartString::new_const(),
|
doc: None,
|
||||||
body: StmtBlock::new(statements, Position::NONE, Position::NONE),
|
body: (!stmt.is_empty()).then(|| stmt.into()),
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
lib: functions.into(),
|
lib: functions.into(),
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
@ -95,11 +104,13 @@ impl AST {
|
|||||||
statements: impl IntoIterator<Item = Stmt>,
|
statements: impl IntoIterator<Item = Stmt>,
|
||||||
#[cfg(not(feature = "no_function"))] functions: impl Into<crate::SharedModule>,
|
#[cfg(not(feature = "no_function"))] functions: impl Into<crate::SharedModule>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let stmt = StmtBlock::new(statements, Position::NONE, Position::NONE);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
source: None,
|
source: None,
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
doc: crate::SmartString::new_const(),
|
doc: None,
|
||||||
body: StmtBlock::new(statements, Position::NONE, Position::NONE),
|
body: (!stmt.is_empty()).then(|| stmt.into()),
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
lib: functions.into(),
|
lib: functions.into(),
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
@ -148,8 +159,8 @@ impl AST {
|
|||||||
Self {
|
Self {
|
||||||
source: None,
|
source: None,
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
doc: crate::SmartString::new_const(),
|
doc: None,
|
||||||
body: StmtBlock::NONE,
|
body: None,
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
lib: crate::Module::new().into(),
|
lib: crate::Module::new().into(),
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
@ -178,11 +189,7 @@ impl AST {
|
|||||||
.as_mut()
|
.as_mut()
|
||||||
.map(|m| m.set_id(source.clone()));
|
.map(|m| m.set_id(source.clone()));
|
||||||
|
|
||||||
if source.is_empty() {
|
self.source = (!source.is_empty()).then(|| source);
|
||||||
self.source = None;
|
|
||||||
} else {
|
|
||||||
self.source = Some(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -202,14 +209,14 @@ impl AST {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn doc(&self) -> &str {
|
pub fn doc(&self) -> &str {
|
||||||
&self.doc
|
self.doc.as_ref().map(|s| s.as_str()).unwrap_or_default()
|
||||||
}
|
}
|
||||||
/// Clear the documentation.
|
/// Clear the documentation.
|
||||||
/// Exported under the `metadata` feature only.
|
/// Exported under the `metadata` feature only.
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn clear_doc(&mut self) -> &mut Self {
|
pub fn clear_doc(&mut self) -> &mut Self {
|
||||||
self.doc.clear();
|
self.doc = None;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
/// Get a mutable reference to the documentation.
|
/// Get a mutable reference to the documentation.
|
||||||
@ -219,8 +226,8 @@ impl AST {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) fn doc_mut(&mut self) -> &mut crate::SmartString {
|
pub(crate) fn doc_mut(&mut self) -> Option<&mut crate::SmartString> {
|
||||||
&mut self.doc
|
self.doc.as_deref_mut()
|
||||||
}
|
}
|
||||||
/// Set the documentation.
|
/// Set the documentation.
|
||||||
///
|
///
|
||||||
@ -228,14 +235,18 @@ impl AST {
|
|||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn set_doc(&mut self, doc: impl Into<crate::SmartString>) {
|
pub(crate) fn set_doc(&mut self, doc: impl Into<crate::SmartString>) {
|
||||||
self.doc = doc.into();
|
let doc = doc.into();
|
||||||
|
self.doc = (!doc.is_empty()).then(|| doc.into());
|
||||||
}
|
}
|
||||||
/// Get the statements.
|
/// Get the statements.
|
||||||
#[cfg(not(feature = "internals"))]
|
#[cfg(not(feature = "internals"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn statements(&self) -> &[Stmt] {
|
pub(crate) fn statements(&self) -> &[Stmt] {
|
||||||
self.body.statements()
|
self.body
|
||||||
|
.as_deref()
|
||||||
|
.map(StmtBlock::statements)
|
||||||
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
/// _(internals)_ Get the statements.
|
/// _(internals)_ Get the statements.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
@ -243,14 +254,20 @@ impl AST {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn statements(&self) -> &[Stmt] {
|
pub fn statements(&self) -> &[Stmt] {
|
||||||
self.body.statements()
|
self.body
|
||||||
|
.as_deref()
|
||||||
|
.map(StmtBlock::statements)
|
||||||
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
/// Extract the statements.
|
/// Extract the statements.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn take_statements(&mut self) -> StmtBlockContainer {
|
pub(crate) fn take_statements(&mut self) -> StmtBlockContainer {
|
||||||
self.body.take_statements()
|
self.body
|
||||||
|
.as_deref_mut()
|
||||||
|
.map(StmtBlock::take_statements)
|
||||||
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
/// Does this [`AST`] contain script-defined functions?
|
/// Does this [`AST`] contain script-defined functions?
|
||||||
///
|
///
|
||||||
@ -344,7 +361,7 @@ impl AST {
|
|||||||
source: self.source.clone(),
|
source: self.source.clone(),
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
doc: self.doc.clone(),
|
doc: self.doc.clone(),
|
||||||
body: StmtBlock::NONE,
|
body: None,
|
||||||
lib: lib.into(),
|
lib: lib.into(),
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
resolver: self.resolver.clone(),
|
resolver: self.resolver.clone(),
|
||||||
@ -542,15 +559,15 @@ impl AST {
|
|||||||
other: &Self,
|
other: &Self,
|
||||||
_filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
|
_filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let merged = match (self.body.is_empty(), other.body.is_empty()) {
|
let merged = match (&self.body, &other.body) {
|
||||||
(false, false) => {
|
(Some(body), Some(other)) => {
|
||||||
let mut body = self.body.clone();
|
let mut body = body.as_ref().clone();
|
||||||
body.extend(other.body.iter().cloned());
|
body.extend(other.iter().cloned());
|
||||||
body
|
body
|
||||||
}
|
}
|
||||||
(false, true) => self.body.clone(),
|
(Some(body), None) => body.as_ref().clone(),
|
||||||
(true, false) => other.body.clone(),
|
(None, Some(body)) => body.as_ref().clone(),
|
||||||
(true, true) => StmtBlock::NONE,
|
(None, None) => StmtBlock::NONE,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
@ -598,11 +615,13 @@ impl AST {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
if !other.doc.is_empty() {
|
if let Some(ref other_doc) = other.doc {
|
||||||
if !_ast.doc.is_empty() {
|
if let Some(ref mut ast_doc) = _ast.doc {
|
||||||
_ast.doc.push('\n');
|
ast_doc.push('\n');
|
||||||
|
ast_doc.push_str(other_doc);
|
||||||
|
} else {
|
||||||
|
_ast.doc = Some(other_doc.clone());
|
||||||
}
|
}
|
||||||
_ast.doc.push_str(other.doc());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_ast
|
_ast
|
||||||
@ -690,7 +709,12 @@ impl AST {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.body.extend(other.body.into_iter());
|
match (&mut self.body, other.body) {
|
||||||
|
(Some(body), Some(other)) => body.extend(other.into_iter()),
|
||||||
|
(Some(_), None) => (),
|
||||||
|
(None, body @ Some(_)) => self.body = body,
|
||||||
|
(None, None) => (),
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
if !other.lib.is_empty() {
|
if !other.lib.is_empty() {
|
||||||
@ -698,11 +722,13 @@ impl AST {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
if !other.doc.is_empty() {
|
if let Some(other_doc) = other.doc {
|
||||||
if !self.doc.is_empty() {
|
if let Some(ref mut self_doc) = self.doc {
|
||||||
self.doc.push('\n');
|
self_doc.push('\n');
|
||||||
|
self_doc.push_str(&other_doc);
|
||||||
|
} else {
|
||||||
|
self.doc = Some(other_doc);
|
||||||
}
|
}
|
||||||
self.doc.push_str(&other.doc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
@ -785,7 +811,7 @@ impl AST {
|
|||||||
/// Clear all statements in the [`AST`], leaving only function definitions.
|
/// Clear all statements in the [`AST`], leaving only function definitions.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn clear_statements(&mut self) -> &mut Self {
|
pub fn clear_statements(&mut self) -> &mut Self {
|
||||||
self.body = StmtBlock::NONE;
|
self.body = None;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
/// Extract all top-level literal constant and/or variable definitions.
|
/// Extract all top-level literal constant and/or variable definitions.
|
||||||
|
@ -185,7 +185,7 @@ impl FnCallHashes {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn script(&self) -> u64 {
|
pub fn script(&self) -> u64 {
|
||||||
assert!(self.script.is_some());
|
debug_assert!(self.script.is_some());
|
||||||
self.script.unwrap()
|
self.script.unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -248,11 +248,7 @@ impl FnCallExpr {
|
|||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn constant_args(&self) -> bool {
|
pub fn constant_args(&self) -> bool {
|
||||||
if self.args.is_empty() {
|
self.args.is_empty() || self.args.iter().all(Expr::is_constant)
|
||||||
true
|
|
||||||
} else {
|
|
||||||
self.args.iter().all(Expr::is_constant)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -383,7 +379,7 @@ impl fmt::Debug for Expr {
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
if !x.1.is_empty() {
|
if !x.1.is_empty() {
|
||||||
write!(f, "{}{}", x.1, Token::DoubleColon.literal_syntax())?;
|
write!(f, "{}{}", x.1, crate::engine::NAMESPACE_SEPARATOR)?;
|
||||||
let pos = x.1.position();
|
let pos = x.1.position();
|
||||||
if !pos.is_none() {
|
if !pos.is_none() {
|
||||||
display_pos = pos;
|
display_pos = pos;
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
#![cfg(not(feature = "no_module"))]
|
#![cfg(not(feature = "no_module"))]
|
||||||
|
|
||||||
use crate::ast::Ident;
|
use crate::ast::Ident;
|
||||||
use crate::tokenizer::Token;
|
|
||||||
use crate::{Position, StaticVec};
|
use crate::{Position, StaticVec};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
@ -46,7 +45,7 @@ impl fmt::Debug for Namespace {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(Ident::as_str)
|
.map(Ident::as_str)
|
||||||
.collect::<StaticVec<_>>()
|
.collect::<StaticVec<_>>()
|
||||||
.join(Token::DoubleColon.literal_syntax()),
|
.join(crate::engine::NAMESPACE_SEPARATOR),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,7 +62,7 @@ impl fmt::Display for Namespace {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(Ident::as_str)
|
.map(Ident::as_str)
|
||||||
.collect::<StaticVec<_>>()
|
.collect::<StaticVec<_>>()
|
||||||
.join(Token::DoubleColon.literal_syntax()),
|
.join(crate::engine::NAMESPACE_SEPARATOR),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ pub struct ScriptFnDef {
|
|||||||
///
|
///
|
||||||
/// Each line in non-block doc-comments starts with `///`.
|
/// Each line in non-block doc-comments starts with `///`.
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
pub comments: Box<[crate::Identifier]>,
|
pub comments: Box<[crate::SmartString]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ScriptFnDef {
|
impl fmt::Display for ScriptFnDef {
|
||||||
|
113
src/ast/stmt.rs
113
src/ast/stmt.rs
@ -1,17 +1,18 @@
|
|||||||
//! Module defining script statements.
|
//! Module defining script statements.
|
||||||
|
|
||||||
use super::{ASTFlags, ASTNode, BinaryExpr, Expr, FnCallExpr, Ident};
|
use super::{ASTFlags, ASTNode, BinaryExpr, Expr, FnCallExpr, Ident};
|
||||||
use crate::engine::KEYWORD_EVAL;
|
use crate::engine::{KEYWORD_EVAL, OP_EQUALS};
|
||||||
|
use crate::func::StraightHashMap;
|
||||||
use crate::tokenizer::Token;
|
use crate::tokenizer::Token;
|
||||||
|
use crate::types::dynamic::Union;
|
||||||
use crate::types::Span;
|
use crate::types::Span;
|
||||||
use crate::{calc_fn_hash, Position, StaticVec, INT};
|
use crate::{calc_fn_hash, Dynamic, Position, StaticVec, INT};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Borrow,
|
borrow::Borrow,
|
||||||
collections::BTreeMap,
|
|
||||||
fmt,
|
fmt,
|
||||||
hash::Hash,
|
hash::{Hash, Hasher},
|
||||||
mem,
|
mem,
|
||||||
num::NonZeroUsize,
|
num::NonZeroUsize,
|
||||||
ops::{Deref, DerefMut, Range, RangeInclusive},
|
ops::{Deref, DerefMut, Range, RangeInclusive},
|
||||||
@ -29,8 +30,12 @@ pub struct OpAssignment {
|
|||||||
hash_op: u64,
|
hash_op: u64,
|
||||||
/// Op-assignment operator.
|
/// Op-assignment operator.
|
||||||
op_assign: Token,
|
op_assign: Token,
|
||||||
|
/// Syntax of op-assignment operator.
|
||||||
|
op_assign_syntax: &'static str,
|
||||||
/// Underlying operator.
|
/// Underlying operator.
|
||||||
op: Token,
|
op: Token,
|
||||||
|
/// Syntax of underlying operator.
|
||||||
|
op_syntax: &'static str,
|
||||||
/// [Position] of the op-assignment operator.
|
/// [Position] of the op-assignment operator.
|
||||||
pos: Position,
|
pos: Position,
|
||||||
}
|
}
|
||||||
@ -44,7 +49,9 @@ impl OpAssignment {
|
|||||||
hash_op_assign: 0,
|
hash_op_assign: 0,
|
||||||
hash_op: 0,
|
hash_op: 0,
|
||||||
op_assign: Token::Equals,
|
op_assign: Token::Equals,
|
||||||
|
op_assign_syntax: OP_EQUALS,
|
||||||
op: Token::Equals,
|
op: Token::Equals,
|
||||||
|
op_syntax: OP_EQUALS,
|
||||||
pos,
|
pos,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,17 +63,28 @@ impl OpAssignment {
|
|||||||
}
|
}
|
||||||
/// Get information if this [`OpAssignment`] is an op-assignment.
|
/// Get information if this [`OpAssignment`] is an op-assignment.
|
||||||
///
|
///
|
||||||
/// Returns `( hash_op_assign, hash_op, op_assign, op )`:
|
/// Returns `( hash_op_assign, hash_op, op_assign, op_assign_syntax, op, op_syntax )`:
|
||||||
///
|
///
|
||||||
/// * `hash_op_assign`: Hash of the op-assignment call.
|
/// * `hash_op_assign`: Hash of the op-assignment call.
|
||||||
/// * `hash_op`: Hash of the underlying operator call (for fallback).
|
/// * `hash_op`: Hash of the underlying operator call (for fallback).
|
||||||
/// * `op_assign`: Op-assignment operator.
|
/// * `op_assign`: Op-assignment operator.
|
||||||
|
/// * `op_assign_syntax`: Syntax of op-assignment operator.
|
||||||
/// * `op`: Underlying operator.
|
/// * `op`: Underlying operator.
|
||||||
|
/// * `op_syntax`: Syntax of underlying operator.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_op_assignment_info(&self) -> Option<(u64, u64, &Token, &Token)> {
|
pub fn get_op_assignment_info(
|
||||||
|
&self,
|
||||||
|
) -> Option<(u64, u64, &Token, &'static str, &Token, &'static str)> {
|
||||||
if self.is_op_assignment() {
|
if self.is_op_assignment() {
|
||||||
Some((self.hash_op_assign, self.hash_op, &self.op_assign, &self.op))
|
Some((
|
||||||
|
self.hash_op_assign,
|
||||||
|
self.hash_op,
|
||||||
|
&self.op_assign,
|
||||||
|
self.op_assign_syntax,
|
||||||
|
&self.op,
|
||||||
|
self.op_syntax,
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -99,11 +117,16 @@ impl OpAssignment {
|
|||||||
.get_base_op_from_assignment()
|
.get_base_op_from_assignment()
|
||||||
.expect("op-assignment operator");
|
.expect("op-assignment operator");
|
||||||
|
|
||||||
|
let op_assign_syntax = op_assign.literal_syntax();
|
||||||
|
let op_syntax = op.literal_syntax();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
hash_op_assign: calc_fn_hash(None, op_assign.literal_syntax(), 2),
|
hash_op_assign: calc_fn_hash(None, op_assign_syntax, 2),
|
||||||
hash_op: calc_fn_hash(None, op.literal_syntax(), 2),
|
hash_op: calc_fn_hash(None, op_syntax, 2),
|
||||||
op_assign,
|
op_assign,
|
||||||
|
op_assign_syntax,
|
||||||
op,
|
op,
|
||||||
|
op_syntax,
|
||||||
pos,
|
pos,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,7 +162,9 @@ impl fmt::Debug for OpAssignment {
|
|||||||
.field("hash_op_assign", &self.hash_op_assign)
|
.field("hash_op_assign", &self.hash_op_assign)
|
||||||
.field("hash_op", &self.hash_op)
|
.field("hash_op", &self.hash_op)
|
||||||
.field("op_assign", &self.op_assign)
|
.field("op_assign", &self.op_assign)
|
||||||
|
.field("op_assign_syntax", &self.op_assign_syntax)
|
||||||
.field("op", &self.op)
|
.field("op", &self.op)
|
||||||
|
.field("op_syntax", &self.op_syntax)
|
||||||
.field("pos", &self.pos)
|
.field("pos", &self.pos)
|
||||||
.finish()
|
.finish()
|
||||||
} else {
|
} else {
|
||||||
@ -233,7 +258,7 @@ impl IntoIterator for RangeCase {
|
|||||||
type Item = INT;
|
type Item = INT;
|
||||||
type IntoIter = Box<dyn Iterator<Item = Self::Item>>;
|
type IntoIter = Box<dyn Iterator<Item = Self::Item>>;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
match self {
|
match self {
|
||||||
@ -245,7 +270,7 @@ impl IntoIterator for RangeCase {
|
|||||||
|
|
||||||
impl RangeCase {
|
impl RangeCase {
|
||||||
/// Returns `true` if the range contains no items.
|
/// Returns `true` if the range contains no items.
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
@ -254,7 +279,7 @@ impl RangeCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Size of the range.
|
/// Size of the range.
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn len(&self) -> INT {
|
pub fn len(&self) -> INT {
|
||||||
match self {
|
match self {
|
||||||
@ -264,15 +289,56 @@ impl RangeCase {
|
|||||||
Self::InclusiveInt(r, ..) => *r.end() - *r.start() + 1,
|
Self::InclusiveInt(r, ..) => *r.end() - *r.start() + 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Is the specified number within this range?
|
/// Is the specified value within this range?
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn contains(&self, n: INT) -> bool {
|
pub fn contains(&self, value: &Dynamic) -> bool {
|
||||||
|
match value {
|
||||||
|
Dynamic(Union::Int(v, ..)) => self.contains_int(*v),
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
Dynamic(Union::Float(v, ..)) => self.contains_float(**v),
|
||||||
|
#[cfg(feature = "decimal")]
|
||||||
|
Dynamic(Union::Decimal(v, ..)) => self.contains_decimal(**v),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Is the specified number within this range?
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn contains_int(&self, n: INT) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::ExclusiveInt(r, ..) => r.contains(&n),
|
Self::ExclusiveInt(r, ..) => r.contains(&n),
|
||||||
Self::InclusiveInt(r, ..) => r.contains(&n),
|
Self::InclusiveInt(r, ..) => r.contains(&n),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Is the specified floating-point number within this range?
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn contains_float(&self, n: crate::FLOAT) -> bool {
|
||||||
|
use crate::FLOAT;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Self::ExclusiveInt(r, ..) => ((r.start as FLOAT)..(r.end as FLOAT)).contains(&n),
|
||||||
|
Self::InclusiveInt(r, ..) => ((*r.start() as FLOAT)..=(*r.end() as FLOAT)).contains(&n),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Is the specified decimal number within this range?
|
||||||
|
#[cfg(feature = "decimal")]
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn contains_decimal(&self, n: rust_decimal::Decimal) -> bool {
|
||||||
|
use rust_decimal::Decimal;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Self::ExclusiveInt(r, ..) => {
|
||||||
|
(Into::<Decimal>::into(r.start)..Into::<Decimal>::into(r.end)).contains(&n)
|
||||||
|
}
|
||||||
|
Self::InclusiveInt(r, ..) => {
|
||||||
|
(Into::<Decimal>::into(*r.start())..=Into::<Decimal>::into(*r.end())).contains(&n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Is the specified range inclusive?
|
/// Is the specified range inclusive?
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@ -303,18 +369,31 @@ pub type CaseBlocksList = smallvec::SmallVec<[usize; 1]>;
|
|||||||
|
|
||||||
/// _(internals)_ A type containing all cases for a `switch` statement.
|
/// _(internals)_ A type containing all cases for a `switch` statement.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SwitchCasesCollection {
|
pub struct SwitchCasesCollection {
|
||||||
/// List of [`ConditionalExpr`]'s.
|
/// List of [`ConditionalExpr`]'s.
|
||||||
pub expressions: StaticVec<ConditionalExpr>,
|
pub expressions: StaticVec<ConditionalExpr>,
|
||||||
/// Dictionary mapping value hashes to [`ConditionalExpr`]'s.
|
/// Dictionary mapping value hashes to [`ConditionalExpr`]'s.
|
||||||
pub cases: BTreeMap<u64, CaseBlocksList>,
|
pub cases: StraightHashMap<CaseBlocksList>,
|
||||||
/// List of range cases.
|
/// List of range cases.
|
||||||
pub ranges: StaticVec<RangeCase>,
|
pub ranges: StaticVec<RangeCase>,
|
||||||
/// Statements block for the default case (there can be no condition for the default case).
|
/// Statements block for the default case (there can be no condition for the default case).
|
||||||
pub def_case: Option<usize>,
|
pub def_case: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Hash for SwitchCasesCollection {
|
||||||
|
#[inline(always)]
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.expressions.hash(state);
|
||||||
|
|
||||||
|
self.cases.len().hash(state);
|
||||||
|
self.cases.iter().for_each(|kv| kv.hash(state));
|
||||||
|
|
||||||
|
self.ranges.hash(state);
|
||||||
|
self.def_case.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Number of items to keep inline for [`StmtBlockContainer`].
|
/// Number of items to keep inline for [`StmtBlockContainer`].
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
const STMT_BLOCK_INLINE_SIZE: usize = 8;
|
const STMT_BLOCK_INLINE_SIZE: usize = 8;
|
||||||
|
@ -2,7 +2,8 @@ use rhai::plugin::*;
|
|||||||
use rhai::{Dynamic, Engine, EvalAltResult, Module, Scope, AST, INT};
|
use rhai::{Dynamic, Engine, EvalAltResult, Module, Scope, AST, INT};
|
||||||
use rustyline::config::Builder;
|
use rustyline::config::Builder;
|
||||||
use rustyline::error::ReadlineError;
|
use rustyline::error::ReadlineError;
|
||||||
use rustyline::{Cmd, Editor, Event, EventHandler, KeyCode, KeyEvent, Modifiers, Movement};
|
use rustyline::history::{History, SearchDirection};
|
||||||
|
use rustyline::{Cmd, DefaultEditor, Event, EventHandler, KeyCode, KeyEvent, Modifiers, Movement};
|
||||||
|
|
||||||
use std::{env, fs::File, io::Read, path::Path, process::exit};
|
use std::{env, fs::File, io::Read, path::Path, process::exit};
|
||||||
|
|
||||||
@ -188,14 +189,14 @@ fn load_script_files(engine: &mut Engine) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Setup the Rustyline editor.
|
// Setup the Rustyline editor.
|
||||||
fn setup_editor() -> Editor<()> {
|
fn setup_editor() -> DefaultEditor {
|
||||||
//env_logger::init();
|
//env_logger::init();
|
||||||
let config = Builder::new()
|
let config = Builder::new()
|
||||||
.tab_stop(4)
|
.tab_stop(4)
|
||||||
.indent_size(4)
|
.indent_size(4)
|
||||||
.bracketed_paste(true)
|
.bracketed_paste(true)
|
||||||
.build();
|
.build();
|
||||||
let mut rl = Editor::<()>::with_config(config).unwrap();
|
let mut rl = DefaultEditor::with_config(config).unwrap();
|
||||||
|
|
||||||
// Bind more keys
|
// Bind more keys
|
||||||
|
|
||||||
@ -336,7 +337,10 @@ fn main() {
|
|||||||
'main_loop: loop {
|
'main_loop: loop {
|
||||||
if let Some(replace) = replacement.take() {
|
if let Some(replace) = replacement.take() {
|
||||||
input = replace;
|
input = replace;
|
||||||
if rl.add_history_entry(input.clone()) {
|
if rl
|
||||||
|
.add_history_entry(input.clone())
|
||||||
|
.expect("Failed to add history entry")
|
||||||
|
{
|
||||||
history_offset += 1;
|
history_offset += 1;
|
||||||
}
|
}
|
||||||
if input.contains('\n') {
|
if input.contains('\n') {
|
||||||
@ -366,7 +370,9 @@ fn main() {
|
|||||||
if !cmd.is_empty()
|
if !cmd.is_empty()
|
||||||
&& !cmd.starts_with('!')
|
&& !cmd.starts_with('!')
|
||||||
&& cmd.trim() != "history"
|
&& cmd.trim() != "history"
|
||||||
&& rl.add_history_entry(input.clone())
|
&& rl
|
||||||
|
.add_history_entry(input.clone())
|
||||||
|
.expect("Failed to add history entry")
|
||||||
{
|
{
|
||||||
history_offset += 1;
|
history_offset += 1;
|
||||||
}
|
}
|
||||||
@ -476,7 +482,7 @@ fn main() {
|
|||||||
|
|
||||||
let json = engine
|
let json = engine
|
||||||
.gen_fn_metadata_with_ast_to_json(&main_ast, false)
|
.gen_fn_metadata_with_ast_to_json(&main_ast, false)
|
||||||
.unwrap();
|
.expect("Unable to generate JSON");
|
||||||
let mut f = std::fs::File::create("metadata.json")
|
let mut f = std::fs::File::create("metadata.json")
|
||||||
.expect("Unable to create `metadata.json`");
|
.expect("Unable to create `metadata.json`");
|
||||||
f.write_all(json.as_bytes()).expect("Unable to write data");
|
f.write_all(json.as_bytes()).expect("Unable to write data");
|
||||||
@ -484,7 +490,7 @@ fn main() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
"!!" => {
|
"!!" => {
|
||||||
match rl.history().last() {
|
match rl.history().iter().last() {
|
||||||
Some(line) => {
|
Some(line) => {
|
||||||
replacement = Some(line.clone());
|
replacement = Some(line.clone());
|
||||||
replacement_index = history_offset + rl.history().len() - 1;
|
replacement_index = history_offset + rl.history().len() - 1;
|
||||||
@ -514,8 +520,12 @@ fn main() {
|
|||||||
_ if cmd.starts_with('!') => {
|
_ if cmd.starts_with('!') => {
|
||||||
if let Ok(num) = cmd[1..].parse::<usize>() {
|
if let Ok(num) = cmd[1..].parse::<usize>() {
|
||||||
if num >= history_offset {
|
if num >= history_offset {
|
||||||
if let Some(line) = rl.history().get(num - history_offset) {
|
if let Some(line) = rl
|
||||||
replacement = Some(line.clone());
|
.history()
|
||||||
|
.get(num - history_offset, SearchDirection::Forward)
|
||||||
|
.expect("Failed to get history entry")
|
||||||
|
{
|
||||||
|
replacement = Some(line.entry.into());
|
||||||
replacement_index = num;
|
replacement_index = num;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -578,7 +588,8 @@ fn main() {
|
|||||||
main_ast.clear_statements();
|
main_ast.clear_statements();
|
||||||
}
|
}
|
||||||
|
|
||||||
rl.save_history(HISTORY_FILE).unwrap();
|
rl.save_history(HISTORY_FILE)
|
||||||
|
.expect("Failed to save history");
|
||||||
|
|
||||||
println!("Bye!");
|
println!("Bye!");
|
||||||
}
|
}
|
||||||
|
152
src/engine.rs
152
src/engine.rs
@ -5,13 +5,11 @@ use crate::func::native::{
|
|||||||
locked_write, OnDebugCallback, OnDefVarCallback, OnParseTokenCallback, OnPrintCallback,
|
locked_write, OnDebugCallback, OnDefVarCallback, OnParseTokenCallback, OnPrintCallback,
|
||||||
OnVarCallback,
|
OnVarCallback,
|
||||||
};
|
};
|
||||||
use crate::module::ModuleFlags;
|
|
||||||
use crate::packages::{Package, StandardPackage};
|
use crate::packages::{Package, StandardPackage};
|
||||||
use crate::tokenizer::Token;
|
use crate::tokenizer::Token;
|
||||||
use crate::types::StringsInterner;
|
use crate::types::StringsInterner;
|
||||||
use crate::{
|
use crate::{
|
||||||
Dynamic, Identifier, ImmutableString, Locked, Module, OptimizationLevel, SharedModule,
|
Dynamic, Identifier, ImmutableString, Locked, OptimizationLevel, SharedModule, StaticVec,
|
||||||
StaticVec,
|
|
||||||
};
|
};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
@ -57,12 +55,19 @@ pub const OP_EQUALS: &str = Token::EqualsTo.literal_syntax();
|
|||||||
/// The `in` operator is implemented as a call to this function.
|
/// The `in` operator is implemented as a call to this function.
|
||||||
pub const OP_CONTAINS: &str = "contains";
|
pub const OP_CONTAINS: &str = "contains";
|
||||||
|
|
||||||
|
/// Standard not operator.
|
||||||
|
pub const OP_NOT: &str = Token::Bang.literal_syntax();
|
||||||
|
|
||||||
/// Standard exclusive range operator.
|
/// Standard exclusive range operator.
|
||||||
pub const OP_EXCLUSIVE_RANGE: &str = Token::ExclusiveRange.literal_syntax();
|
pub const OP_EXCLUSIVE_RANGE: &str = Token::ExclusiveRange.literal_syntax();
|
||||||
|
|
||||||
/// Standard inclusive range operator.
|
/// Standard inclusive range operator.
|
||||||
pub const OP_INCLUSIVE_RANGE: &str = Token::InclusiveRange.literal_syntax();
|
pub const OP_INCLUSIVE_RANGE: &str = Token::InclusiveRange.literal_syntax();
|
||||||
|
|
||||||
|
/// Separator for namespaces.
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
pub const NAMESPACE_SEPARATOR: &str = Token::DoubleColon.literal_syntax();
|
||||||
|
|
||||||
/// Rhai main scripting engine.
|
/// Rhai main scripting engine.
|
||||||
///
|
///
|
||||||
/// # Thread Safety
|
/// # Thread Safety
|
||||||
@ -96,10 +101,10 @@ pub struct Engine {
|
|||||||
|
|
||||||
/// A module resolution service.
|
/// A module resolution service.
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
pub(crate) module_resolver: Box<dyn crate::ModuleResolver>,
|
pub(crate) module_resolver: Option<Box<dyn crate::ModuleResolver>>,
|
||||||
|
|
||||||
/// Strings interner.
|
/// Strings interner.
|
||||||
pub(crate) interned_strings: Locked<StringsInterner>,
|
pub(crate) interned_strings: Option<Box<Locked<StringsInterner>>>,
|
||||||
|
|
||||||
/// A set of symbols to disable.
|
/// A set of symbols to disable.
|
||||||
pub(crate) disabled_symbols: Option<Box<BTreeSet<Identifier>>>,
|
pub(crate) disabled_symbols: Option<Box<BTreeSet<Identifier>>>,
|
||||||
@ -110,7 +115,7 @@ pub struct Engine {
|
|||||||
/// Custom syntax.
|
/// Custom syntax.
|
||||||
#[cfg(not(feature = "no_custom_syntax"))]
|
#[cfg(not(feature = "no_custom_syntax"))]
|
||||||
pub(crate) custom_syntax: Option<
|
pub(crate) custom_syntax: Option<
|
||||||
Box<std::collections::BTreeMap<Identifier, crate::api::custom_syntax::CustomSyntax>>,
|
Box<std::collections::BTreeMap<Identifier, Box<crate::api::custom_syntax::CustomSyntax>>>,
|
||||||
>,
|
>,
|
||||||
/// Callback closure for filtering variable definition.
|
/// Callback closure for filtering variable definition.
|
||||||
pub(crate) def_var_filter: Option<Box<OnDefVarCallback>>,
|
pub(crate) def_var_filter: Option<Box<OnDefVarCallback>>,
|
||||||
@ -120,9 +125,9 @@ pub struct Engine {
|
|||||||
pub(crate) token_mapper: Option<Box<OnParseTokenCallback>>,
|
pub(crate) token_mapper: Option<Box<OnParseTokenCallback>>,
|
||||||
|
|
||||||
/// Callback closure for implementing the `print` command.
|
/// Callback closure for implementing the `print` command.
|
||||||
pub(crate) print: Box<OnPrintCallback>,
|
pub(crate) print: Option<Box<OnPrintCallback>>,
|
||||||
/// Callback closure for implementing the `debug` command.
|
/// Callback closure for implementing the `debug` command.
|
||||||
pub(crate) debug: Box<OnDebugCallback>,
|
pub(crate) debug: Option<Box<OnDebugCallback>>,
|
||||||
/// Callback closure for progress reporting.
|
/// Callback closure for progress reporting.
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
pub(crate) progress: Option<Box<crate::func::native::OnProgressCallback>>,
|
pub(crate) progress: Option<Box<crate::func::native::OnProgressCallback>>,
|
||||||
@ -142,12 +147,10 @@ pub struct Engine {
|
|||||||
|
|
||||||
/// Callback closure for debugging.
|
/// Callback closure for debugging.
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
pub(crate) debugger_interface: Option<
|
pub(crate) debugger_interface: Option<(
|
||||||
Box<(
|
|
||||||
Box<crate::eval::OnDebuggingInit>,
|
Box<crate::eval::OnDebuggingInit>,
|
||||||
Box<crate::eval::OnDebuggerCallback>,
|
Box<crate::eval::OnDebuggerCallback>,
|
||||||
)>,
|
)>,
|
||||||
>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Engine {
|
impl fmt::Debug for Engine {
|
||||||
@ -224,56 +227,17 @@ pub fn make_setter(id: &str) -> Identifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
/// Create a new [`Engine`].
|
/// An empty raw [`Engine`].
|
||||||
#[inline]
|
pub const RAW: Self = Self {
|
||||||
#[must_use]
|
|
||||||
pub fn new() -> Self {
|
|
||||||
// Create the new scripting Engine
|
|
||||||
let mut engine = Self::new_raw();
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
#[cfg(not(feature = "no_std"))]
|
|
||||||
#[cfg(not(target_family = "wasm"))]
|
|
||||||
{
|
|
||||||
engine.module_resolver = Box::new(crate::module::resolvers::FileModuleResolver::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
// default print/debug implementations
|
|
||||||
#[cfg(not(feature = "no_std"))]
|
|
||||||
#[cfg(not(target_family = "wasm"))]
|
|
||||||
{
|
|
||||||
engine.print = Box::new(|s| println!("{s}"));
|
|
||||||
engine.debug = Box::new(|s, source, pos| match (source, pos) {
|
|
||||||
(Some(source), crate::Position::NONE) => println!("{source} | {s}"),
|
|
||||||
#[cfg(not(feature = "no_position"))]
|
|
||||||
(Some(source), pos) => println!("{source} @ {pos:?} | {s}"),
|
|
||||||
(None, crate::Position::NONE) => println!("{s}"),
|
|
||||||
#[cfg(not(feature = "no_position"))]
|
|
||||||
(None, pos) => println!("{pos:?} | {s}"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
engine.register_global_module(StandardPackage::new().as_shared_module());
|
|
||||||
|
|
||||||
engine
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new [`Engine`] with minimal built-in functions.
|
|
||||||
///
|
|
||||||
/// Use [`register_global_module`][Engine::register_global_module] to add packages of functions.
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
pub fn new_raw() -> Self {
|
|
||||||
let mut engine = Self {
|
|
||||||
global_modules: StaticVec::new_const(),
|
global_modules: StaticVec::new_const(),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
global_sub_modules: None,
|
global_sub_modules: None,
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
module_resolver: Box::new(crate::module::resolvers::DummyModuleResolver::new()),
|
module_resolver: None,
|
||||||
|
|
||||||
interned_strings: StringsInterner::new().into(),
|
interned_strings: None,
|
||||||
disabled_symbols: None,
|
disabled_symbols: None,
|
||||||
#[cfg(not(feature = "no_custom_syntax"))]
|
#[cfg(not(feature = "no_custom_syntax"))]
|
||||||
custom_keywords: None,
|
custom_keywords: None,
|
||||||
@ -284,8 +248,8 @@ impl Engine {
|
|||||||
resolve_var: None,
|
resolve_var: None,
|
||||||
token_mapper: None,
|
token_mapper: None,
|
||||||
|
|
||||||
print: Box::new(|_| {}),
|
print: None,
|
||||||
debug: Box::new(|_, _, _| {}),
|
debug: None,
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
progress: None,
|
progress: None,
|
||||||
@ -306,14 +270,55 @@ impl Engine {
|
|||||||
debugger_interface: None,
|
debugger_interface: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add the global namespace module
|
/// Create a new [`Engine`].
|
||||||
let mut global_namespace = Module::new();
|
#[inline]
|
||||||
global_namespace.flags |= ModuleFlags::INTERNAL;
|
#[must_use]
|
||||||
engine.global_modules.push(global_namespace.into());
|
pub fn new() -> Self {
|
||||||
|
// Create the new scripting Engine
|
||||||
|
let mut engine = Self::new_raw();
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
|
{
|
||||||
|
engine.module_resolver =
|
||||||
|
Some(Box::new(crate::module::resolvers::FileModuleResolver::new()));
|
||||||
|
}
|
||||||
|
|
||||||
|
engine.interned_strings = Some(Locked::new(StringsInterner::new()).into());
|
||||||
|
|
||||||
|
// default print/debug implementations
|
||||||
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
|
{
|
||||||
|
engine.print = Some(Box::new(|s| println!("{s}")));
|
||||||
|
engine.debug = Some(Box::new(|s, source, pos| match (source, pos) {
|
||||||
|
(Some(source), crate::Position::NONE) => println!("{source} | {s}"),
|
||||||
|
#[cfg(not(feature = "no_position"))]
|
||||||
|
(Some(source), pos) => println!("{source} @ {pos:?} | {s}"),
|
||||||
|
(None, crate::Position::NONE) => println!("{s}"),
|
||||||
|
#[cfg(not(feature = "no_position"))]
|
||||||
|
(None, pos) => println!("{pos:?} | {s}"),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
engine.register_global_module(StandardPackage::new().as_shared_module());
|
||||||
|
|
||||||
engine
|
engine
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new [`Engine`] with minimal built-in functions.
|
||||||
|
/// It returns a copy of [`Engine::RAW`].
|
||||||
|
///
|
||||||
|
/// This is useful for creating a custom scripting engine with only the functions you need.
|
||||||
|
///
|
||||||
|
/// Use [`register_global_module`][Engine::register_global_module] to add packages of functions.
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new_raw() -> Self {
|
||||||
|
Self::RAW
|
||||||
|
}
|
||||||
|
|
||||||
/// Get an interned [string][ImmutableString].
|
/// Get an interned [string][ImmutableString].
|
||||||
#[cfg(not(feature = "internals"))]
|
#[cfg(not(feature = "internals"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -322,7 +327,11 @@ impl Engine {
|
|||||||
&self,
|
&self,
|
||||||
string: impl AsRef<str> + Into<ImmutableString>,
|
string: impl AsRef<str> + Into<ImmutableString>,
|
||||||
) -> ImmutableString {
|
) -> ImmutableString {
|
||||||
locked_write(&self.interned_strings).get(string)
|
if let Some(ref interner) = self.interned_strings {
|
||||||
|
locked_write(interner).get(string)
|
||||||
|
} else {
|
||||||
|
string.into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// _(internals)_ Get an interned [string][ImmutableString].
|
/// _(internals)_ Get an interned [string][ImmutableString].
|
||||||
@ -331,13 +340,17 @@ impl Engine {
|
|||||||
/// [`Engine`] keeps a cache of [`ImmutableString`] instances and tries to avoid new allocations
|
/// [`Engine`] keeps a cache of [`ImmutableString`] instances and tries to avoid new allocations
|
||||||
/// when an existing instance is found.
|
/// when an existing instance is found.
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn get_interned_string(
|
pub fn get_interned_string(
|
||||||
&self,
|
&self,
|
||||||
string: impl AsRef<str> + Into<ImmutableString>,
|
string: impl AsRef<str> + Into<ImmutableString>,
|
||||||
) -> ImmutableString {
|
) -> ImmutableString {
|
||||||
locked_write(&self.interned_strings).get(string)
|
if let Some(ref interner) = self.interned_strings {
|
||||||
|
locked_write(interner).get(string)
|
||||||
|
} else {
|
||||||
|
string.into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an empty [`ImmutableString`] which refers to a shared instance.
|
/// Get an empty [`ImmutableString`] which refers to a shared instance.
|
||||||
@ -354,4 +367,17 @@ impl Engine {
|
|||||||
pub(crate) const fn is_debugger_registered(&self) -> bool {
|
pub(crate) const fn is_debugger_registered(&self) -> bool {
|
||||||
self.debugger_interface.is_some()
|
self.debugger_interface.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Imitation of std::hints::black_box which requires nightly.
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
|
#[inline(never)]
|
||||||
|
pub(crate) fn black_box() -> usize {
|
||||||
|
unsafe { core::ptr::read_volatile(&0_usize as *const usize) }
|
||||||
|
}
|
||||||
|
/// Imitation of std::hints::black_box which requires nightly.
|
||||||
|
#[cfg(target_family = "wasm")]
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn black_box() -> usize {
|
||||||
|
0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,8 +63,8 @@ impl Caches {
|
|||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn fn_resolution_cache_mut(&mut self) -> &mut FnResolutionCache {
|
pub fn fn_resolution_cache_mut(&mut self) -> &mut FnResolutionCache {
|
||||||
if self.0.is_empty() {
|
|
||||||
// Push a new function resolution cache if the stack is empty
|
// Push a new function resolution cache if the stack is empty
|
||||||
|
if self.0.is_empty() {
|
||||||
self.push_fn_resolution_cache();
|
self.push_fn_resolution_cache();
|
||||||
}
|
}
|
||||||
self.0.last_mut().unwrap()
|
self.0.last_mut().unwrap()
|
||||||
|
@ -504,10 +504,8 @@ impl Engine {
|
|||||||
global, caches, scope, this_ptr, expr, rhs, idx_values,
|
global, caches, scope, this_ptr, expr, rhs, idx_values,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if !_arg_values.is_empty() {
|
|
||||||
idx_values.extend(_arg_values);
|
idx_values.extend(_arg_values);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
_ if chain_type == ChainType::Dotting => {
|
_ if chain_type == ChainType::Dotting => {
|
||||||
|
@ -8,7 +8,6 @@ use std::borrow::Borrow;
|
|||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
|
|
||||||
impl Dynamic {
|
|
||||||
/// Recursively calculate the sizes of an array.
|
/// Recursively calculate the sizes of an array.
|
||||||
///
|
///
|
||||||
/// Sizes returned are `(` [`Array`][crate::Array], [`Map`][crate::Map] and [`String`] `)`.
|
/// Sizes returned are `(` [`Array`][crate::Array], [`Map`][crate::Map] and [`String`] `)`.
|
||||||
@ -18,7 +17,7 @@ impl Dynamic {
|
|||||||
/// Panics if any interior data is shared (should never happen).
|
/// Panics if any interior data is shared (should never happen).
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn calc_array_sizes(array: &crate::Array) -> (usize, usize, usize) {
|
pub fn calc_array_sizes(array: &crate::Array) -> (usize, usize, usize) {
|
||||||
let (mut ax, mut mx, mut sx) = (0, 0, 0);
|
let (mut ax, mut mx, mut sx) = (0, 0, 0);
|
||||||
|
|
||||||
for value in array {
|
for value in array {
|
||||||
@ -26,7 +25,7 @@ impl Dynamic {
|
|||||||
|
|
||||||
match value.0 {
|
match value.0 {
|
||||||
Union::Array(ref a, ..) => {
|
Union::Array(ref a, ..) => {
|
||||||
let (a, m, s) = Self::calc_array_sizes(a);
|
let (a, m, s) = calc_array_sizes(a);
|
||||||
ax += a;
|
ax += a;
|
||||||
mx += m;
|
mx += m;
|
||||||
sx += s;
|
sx += s;
|
||||||
@ -34,7 +33,7 @@ impl Dynamic {
|
|||||||
Union::Blob(ref a, ..) => ax += 1 + a.len(),
|
Union::Blob(ref a, ..) => ax += 1 + a.len(),
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Union::Map(ref m, ..) => {
|
Union::Map(ref m, ..) => {
|
||||||
let (a, m, s) = Self::calc_map_sizes(m);
|
let (a, m, s) = calc_map_sizes(m);
|
||||||
ax += a;
|
ax += a;
|
||||||
mx += m;
|
mx += m;
|
||||||
sx += s;
|
sx += s;
|
||||||
@ -59,7 +58,7 @@ impl Dynamic {
|
|||||||
/// Panics if any interior data is shared (should never happen).
|
/// Panics if any interior data is shared (should never happen).
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn calc_map_sizes(map: &crate::Map) -> (usize, usize, usize) {
|
pub fn calc_map_sizes(map: &crate::Map) -> (usize, usize, usize) {
|
||||||
let (mut ax, mut mx, mut sx) = (0, 0, 0);
|
let (mut ax, mut mx, mut sx) = (0, 0, 0);
|
||||||
|
|
||||||
for value in map.values() {
|
for value in map.values() {
|
||||||
@ -68,7 +67,7 @@ impl Dynamic {
|
|||||||
match value.0 {
|
match value.0 {
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Union::Array(ref a, ..) => {
|
Union::Array(ref a, ..) => {
|
||||||
let (a, m, s) = Self::calc_array_sizes(a);
|
let (a, m, s) = calc_array_sizes(a);
|
||||||
ax += a;
|
ax += a;
|
||||||
mx += m;
|
mx += m;
|
||||||
sx += s;
|
sx += s;
|
||||||
@ -76,7 +75,7 @@ impl Dynamic {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Union::Blob(ref a, ..) => ax += 1 + a.len(),
|
Union::Blob(ref a, ..) => ax += 1 + a.len(),
|
||||||
Union::Map(ref m, ..) => {
|
Union::Map(ref m, ..) => {
|
||||||
let (a, m, s) = Self::calc_map_sizes(m);
|
let (a, m, s) = calc_map_sizes(m);
|
||||||
ax += a;
|
ax += a;
|
||||||
mx += m;
|
mx += m;
|
||||||
sx += s;
|
sx += s;
|
||||||
@ -92,6 +91,8 @@ impl Dynamic {
|
|||||||
|
|
||||||
(ax, mx, sx)
|
(ax, mx, sx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Dynamic {
|
||||||
/// Recursively calculate the sizes of a value.
|
/// Recursively calculate the sizes of a value.
|
||||||
///
|
///
|
||||||
/// Sizes returned are `(` [`Array`][crate::Array], [`Map`][crate::Map] and [`String`] `)`.
|
/// Sizes returned are `(` [`Array`][crate::Array], [`Map`][crate::Map] and [`String`] `)`.
|
||||||
@ -103,11 +104,11 @@ impl Dynamic {
|
|||||||
pub(crate) fn calc_data_sizes(&self, _top: bool) -> (usize, usize, usize) {
|
pub(crate) fn calc_data_sizes(&self, _top: bool) -> (usize, usize, usize) {
|
||||||
match self.0 {
|
match self.0 {
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Union::Array(ref arr, ..) => Self::calc_array_sizes(arr),
|
Union::Array(ref arr, ..) => calc_array_sizes(arr),
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Union::Blob(ref blob, ..) => (blob.len(), 0, 0),
|
Union::Blob(ref blob, ..) => (blob.len(), 0, 0),
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Union::Map(ref map, ..) => Self::calc_map_sizes(map),
|
Union::Map(ref map, ..) => calc_map_sizes(map),
|
||||||
Union::Str(ref s, ..) => (0, 0, s.len()),
|
Union::Str(ref s, ..) => (0, 0, s.len()),
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
Union::Shared(..) if _top => self.read_lock::<Self>().unwrap().calc_data_sizes(true),
|
Union::Shared(..) if _top => self.read_lock::<Self>().unwrap().calc_data_sizes(true),
|
||||||
@ -190,6 +191,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the number of operations stay within limit.
|
/// Check if the number of operations stay within limit.
|
||||||
|
#[inline(always)]
|
||||||
pub(crate) fn track_operation(
|
pub(crate) fn track_operation(
|
||||||
&self,
|
&self,
|
||||||
global: &mut GlobalRuntimeState,
|
global: &mut GlobalRuntimeState,
|
||||||
@ -198,16 +200,16 @@ impl Engine {
|
|||||||
global.num_operations += 1;
|
global.num_operations += 1;
|
||||||
|
|
||||||
// Guard against too many operations
|
// Guard against too many operations
|
||||||
let max = self.max_operations();
|
if self.max_operations() > 0 && global.num_operations > self.max_operations() {
|
||||||
|
Err(ERR::ErrorTooManyOperations(pos).into())
|
||||||
if max > 0 && global.num_operations > max {
|
} else {
|
||||||
return Err(ERR::ErrorTooManyOperations(pos).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Report progress
|
|
||||||
self.progress
|
self.progress
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|p| p(global.num_operations))
|
.and_then(|progress| {
|
||||||
.map_or(Ok(()), |token| Err(ERR::ErrorTerminated(token, pos).into()))
|
progress(global.num_operations)
|
||||||
|
.map(|token| Err(ERR::ErrorTerminated(token, pos).into()))
|
||||||
|
})
|
||||||
|
.unwrap_or(Ok(()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -510,7 +510,7 @@ impl Engine {
|
|||||||
let src = global.source_raw().cloned();
|
let src = global.source_raw().cloned();
|
||||||
let src = src.as_ref().map(|s| s.as_str());
|
let src = src.as_ref().map(|s| s.as_str());
|
||||||
let context = EvalContext::new(self, global, caches, scope, this_ptr);
|
let context = EvalContext::new(self, global, caches, scope, this_ptr);
|
||||||
let (.., ref on_debugger) = **x;
|
let (.., ref on_debugger) = *x;
|
||||||
|
|
||||||
let command = on_debugger(context, event, node, src, node.position());
|
let command = on_debugger(context, event, node, src, node.position());
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ impl Engine {
|
|||||||
global: &GlobalRuntimeState,
|
global: &GlobalRuntimeState,
|
||||||
namespace: &crate::ast::Namespace,
|
namespace: &crate::ast::Namespace,
|
||||||
) -> Option<crate::SharedModule> {
|
) -> Option<crate::SharedModule> {
|
||||||
assert!(!namespace.is_empty());
|
debug_assert!(!namespace.is_empty());
|
||||||
|
|
||||||
let root = namespace.root();
|
let root = namespace.root();
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ impl Engine {
|
|||||||
if let Some(module) = self.search_imports(global, ns) {
|
if let Some(module) = self.search_imports(global, ns) {
|
||||||
return module.get_qualified_var(*hash_var).map_or_else(
|
return module.get_qualified_var(*hash_var).map_or_else(
|
||||||
|| {
|
|| {
|
||||||
let sep = crate::tokenizer::Token::DoubleColon.literal_syntax();
|
let sep = crate::engine::NAMESPACE_SEPARATOR;
|
||||||
|
|
||||||
Err(ERR::ErrorVariableNotFound(
|
Err(ERR::ErrorVariableNotFound(
|
||||||
format!("{ns}{sep}{var_name}"),
|
format!("{ns}{sep}{var_name}"),
|
||||||
@ -104,7 +104,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let sep = crate::tokenizer::Token::DoubleColon.literal_syntax();
|
let sep = crate::engine::NAMESPACE_SEPARATOR;
|
||||||
|
|
||||||
return Err(ERR::ErrorVariableNotFound(
|
return Err(ERR::ErrorVariableNotFound(
|
||||||
format!("{ns}{sep}{var_name}"),
|
format!("{ns}{sep}{var_name}"),
|
||||||
@ -239,9 +239,6 @@ impl Engine {
|
|||||||
// Coded this way for better branch prediction.
|
// Coded this way for better branch prediction.
|
||||||
// Popular branches are lifted out of the `match` statement into their own branches.
|
// Popular branches are lifted out of the `match` statement into their own branches.
|
||||||
|
|
||||||
// Function calls should account for a relatively larger portion of expressions because
|
|
||||||
// binary operators are also function calls.
|
|
||||||
if let Expr::FnCall(x, pos) = expr {
|
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
let reset =
|
let reset =
|
||||||
self.run_debugger_with_reset(global, caches, scope, this_ptr.as_deref_mut(), expr)?;
|
self.run_debugger_with_reset(global, caches, scope, this_ptr.as_deref_mut(), expr)?;
|
||||||
@ -250,18 +247,14 @@ impl Engine {
|
|||||||
|
|
||||||
self.track_operation(global, expr.position())?;
|
self.track_operation(global, expr.position())?;
|
||||||
|
|
||||||
|
// Function calls should account for a relatively larger portion of expressions because
|
||||||
|
// binary operators are also function calls.
|
||||||
|
if let Expr::FnCall(x, pos) = expr {
|
||||||
return self.eval_fn_call_expr(global, caches, scope, this_ptr, x, *pos);
|
return self.eval_fn_call_expr(global, caches, scope, this_ptr, x, *pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then variable access.
|
// Then variable access.
|
||||||
// We shouldn't do this for too many variants because, soon or later, the added comparisons
|
|
||||||
// will cost more than the mis-predicted `match` branch.
|
|
||||||
if let Expr::Variable(x, index, var_pos) = expr {
|
if let Expr::Variable(x, index, var_pos) = expr {
|
||||||
#[cfg(feature = "debugging")]
|
|
||||||
self.run_debugger(global, caches, scope, this_ptr.as_deref_mut(), expr)?;
|
|
||||||
|
|
||||||
self.track_operation(global, expr.position())?;
|
|
||||||
|
|
||||||
return if index.is_none() && x.0.is_none() && x.3 == KEYWORD_THIS {
|
return if index.is_none() && x.0.is_none() && x.3 == KEYWORD_THIS {
|
||||||
this_ptr
|
this_ptr
|
||||||
.ok_or_else(|| ERR::ErrorUnboundThis(*var_pos).into())
|
.ok_or_else(|| ERR::ErrorUnboundThis(*var_pos).into())
|
||||||
@ -272,24 +265,26 @@ impl Engine {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "debugging")]
|
// Then integer constants.
|
||||||
let reset =
|
if let Expr::IntegerConstant(x, ..) = expr {
|
||||||
self.run_debugger_with_reset(global, caches, scope, this_ptr.as_deref_mut(), expr)?;
|
return Ok((*x).into());
|
||||||
#[cfg(feature = "debugging")]
|
}
|
||||||
auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset));
|
|
||||||
|
|
||||||
self.track_operation(global, expr.position())?;
|
// Stop merging branches here!
|
||||||
|
// We shouldn't lift out too many variants because, soon or later, the added comparisons
|
||||||
|
// will cost more than the mis-predicted `match` branch.
|
||||||
|
Self::black_box();
|
||||||
|
|
||||||
match expr {
|
match expr {
|
||||||
// Constants
|
// Constants
|
||||||
Expr::DynamicConstant(x, ..) => Ok(x.as_ref().clone()),
|
Expr::IntegerConstant(..) => unreachable!(),
|
||||||
Expr::IntegerConstant(x, ..) => Ok((*x).into()),
|
Expr::StringConstant(x, ..) => Ok(x.clone().into()),
|
||||||
|
Expr::BoolConstant(x, ..) => Ok((*x).into()),
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Expr::FloatConstant(x, ..) => Ok((*x).into()),
|
Expr::FloatConstant(x, ..) => Ok((*x).into()),
|
||||||
Expr::StringConstant(x, ..) => Ok(x.clone().into()),
|
|
||||||
Expr::CharConstant(x, ..) => Ok((*x).into()),
|
Expr::CharConstant(x, ..) => Ok((*x).into()),
|
||||||
Expr::BoolConstant(x, ..) => Ok((*x).into()),
|
|
||||||
Expr::Unit(..) => Ok(Dynamic::UNIT),
|
Expr::Unit(..) => Ok(Dynamic::UNIT),
|
||||||
|
Expr::DynamicConstant(x, ..) => Ok(x.as_ref().clone()),
|
||||||
|
|
||||||
// `... ${...} ...`
|
// `... ${...} ...`
|
||||||
Expr::InterpolatedString(x, _) => {
|
Expr::InterpolatedString(x, _) => {
|
||||||
@ -434,8 +429,13 @@ impl Engine {
|
|||||||
.and_then(|r| self.check_data_size(r, expr.start_position()))
|
.and_then(|r| self.check_data_size(r, expr.start_position()))
|
||||||
}
|
}
|
||||||
|
|
||||||
Expr::Stmt(x) if x.is_empty() => Ok(Dynamic::UNIT),
|
Expr::Stmt(x) => {
|
||||||
Expr::Stmt(x) => self.eval_stmt_block(global, caches, scope, this_ptr, x, true),
|
if x.is_empty() {
|
||||||
|
Ok(Dynamic::UNIT)
|
||||||
|
} else {
|
||||||
|
self.eval_stmt_block(global, caches, scope, this_ptr, x, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(..) => {
|
Expr::Index(..) => {
|
||||||
|
@ -127,20 +127,6 @@ impl GlobalRuntimeState {
|
|||||||
pub fn get_shared_import(&self, index: usize) -> Option<crate::SharedModule> {
|
pub fn get_shared_import(&self, index: usize) -> Option<crate::SharedModule> {
|
||||||
self.modules.as_ref().and_then(|m| m.get(index).cloned())
|
self.modules.as_ref().and_then(|m| m.get(index).cloned())
|
||||||
}
|
}
|
||||||
/// Get a mutable reference to the globally-imported [module][crate::Module] at a
|
|
||||||
/// particular index.
|
|
||||||
///
|
|
||||||
/// Not available under `no_module`.
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
pub(crate) fn get_shared_import_mut(
|
|
||||||
&mut self,
|
|
||||||
index: usize,
|
|
||||||
) -> Option<&mut crate::SharedModule> {
|
|
||||||
self.modules.as_deref_mut().and_then(|m| m.get_mut(index))
|
|
||||||
}
|
|
||||||
/// Get the index of a globally-imported [module][crate::Module] by name.
|
/// Get the index of a globally-imported [module][crate::Module] by name.
|
||||||
///
|
///
|
||||||
/// Not available under `no_module`.
|
/// Not available under `no_module`.
|
||||||
|
@ -11,6 +11,12 @@ mod target;
|
|||||||
pub use cache::{Caches, FnResolutionCache, FnResolutionCacheEntry};
|
pub use cache::{Caches, FnResolutionCache, FnResolutionCacheEntry};
|
||||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||||
pub use chaining::ChainType;
|
pub use chaining::ChainType;
|
||||||
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
pub use data_check::calc_array_sizes;
|
||||||
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
pub use data_check::calc_map_sizes;
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
pub use debugger::{
|
pub use debugger::{
|
||||||
BreakPoint, CallStackFrame, Debugger, DebuggerCommand, DebuggerEvent, DebuggerStatus,
|
BreakPoint, CallStackFrame, Debugger, DebuggerCommand, DebuggerEvent, DebuggerStatus,
|
||||||
|
366
src/eval/stmt.rs
366
src/eval/stmt.rs
@ -3,9 +3,11 @@
|
|||||||
use super::{Caches, EvalContext, GlobalRuntimeState, Target};
|
use super::{Caches, EvalContext, GlobalRuntimeState, Target};
|
||||||
use crate::api::events::VarDefInfo;
|
use crate::api::events::VarDefInfo;
|
||||||
use crate::ast::{
|
use crate::ast::{
|
||||||
ASTFlags, BinaryExpr, Expr, FlowControl, OpAssignment, Stmt, SwitchCasesCollection,
|
ASTFlags, BinaryExpr, ConditionalExpr, Expr, FlowControl, OpAssignment, Stmt,
|
||||||
|
SwitchCasesCollection,
|
||||||
};
|
};
|
||||||
use crate::func::{get_builtin_op_assignment_fn, get_hasher};
|
use crate::func::{get_builtin_op_assignment_fn, get_hasher};
|
||||||
|
use crate::tokenizer::Token;
|
||||||
use crate::types::dynamic::{AccessMode, Union};
|
use crate::types::dynamic::{AccessMode, Union};
|
||||||
use crate::{Dynamic, Engine, RhaiResult, RhaiResultOf, Scope, ERR, INT};
|
use crate::{Dynamic, Engine, RhaiResult, RhaiResultOf, Scope, ERR, INT};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
@ -127,48 +129,119 @@ impl Engine {
|
|||||||
|
|
||||||
let pos = op_info.position();
|
let pos = op_info.position();
|
||||||
|
|
||||||
if let Some((hash1, hash2, op_assign, op)) = op_info.get_op_assignment_info() {
|
if let Some((hash_x, hash, op_x, op_x_str, op, op_str)) = op_info.get_op_assignment_info() {
|
||||||
let mut lock_guard = target.write_lock::<Dynamic>().unwrap();
|
let mut lock_guard = target.write_lock::<Dynamic>().unwrap();
|
||||||
|
let mut done = false;
|
||||||
|
|
||||||
|
// Short-circuit built-in op-assignments if under Fast Operators mode
|
||||||
|
if self.fast_operators() {
|
||||||
|
#[allow(clippy::wildcard_imports)]
|
||||||
|
use Token::*;
|
||||||
|
|
||||||
|
done = true;
|
||||||
|
|
||||||
|
// For extremely simple primary data operations, do it directly
|
||||||
|
// to avoid the overhead of calling a function.
|
||||||
|
match (&mut lock_guard.0, &mut new_val.0) {
|
||||||
|
(Union::Bool(b1, ..), Union::Bool(b2, ..)) => match op_x {
|
||||||
|
AndAssign => *b1 = *b1 && *b2,
|
||||||
|
OrAssign => *b1 = *b1 || *b2,
|
||||||
|
XOrAssign => *b1 = *b1 ^ *b2,
|
||||||
|
_ => done = false,
|
||||||
|
},
|
||||||
|
(Union::Int(n1, ..), Union::Int(n2, ..)) => {
|
||||||
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
#[allow(clippy::wildcard_imports)]
|
||||||
|
use crate::packages::arithmetic::arith_basic::INT::functions::*;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
match op_x {
|
||||||
|
PlusAssign => {
|
||||||
|
*n1 = add(*n1, *n2).map_err(|err| err.fill_position(pos))?
|
||||||
|
}
|
||||||
|
MinusAssign => {
|
||||||
|
*n1 = subtract(*n1, *n2).map_err(|err| err.fill_position(pos))?
|
||||||
|
}
|
||||||
|
MultiplyAssign => {
|
||||||
|
*n1 = multiply(*n1, *n2).map_err(|err| err.fill_position(pos))?
|
||||||
|
}
|
||||||
|
DivideAssign => {
|
||||||
|
*n1 = divide(*n1, *n2).map_err(|err| err.fill_position(pos))?
|
||||||
|
}
|
||||||
|
ModuloAssign => {
|
||||||
|
*n1 = modulo(*n1, *n2).map_err(|err| err.fill_position(pos))?
|
||||||
|
}
|
||||||
|
_ => done = false,
|
||||||
|
}
|
||||||
|
#[cfg(feature = "unchecked")]
|
||||||
|
match op_x {
|
||||||
|
PlusAssign => *n1 += *n2,
|
||||||
|
MinusAssign => *n1 -= *n2,
|
||||||
|
MultiplyAssign => *n1 *= *n2,
|
||||||
|
DivideAssign => *n1 /= *n2,
|
||||||
|
ModuloAssign => *n1 %= *n2,
|
||||||
|
_ => done = false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
(Union::Float(f1, ..), Union::Float(f2, ..)) => match op_x {
|
||||||
|
PlusAssign => **f1 += **f2,
|
||||||
|
MinusAssign => **f1 -= **f2,
|
||||||
|
MultiplyAssign => **f1 *= **f2,
|
||||||
|
DivideAssign => **f1 /= **f2,
|
||||||
|
ModuloAssign => **f1 %= **f2,
|
||||||
|
_ => done = false,
|
||||||
|
},
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
(Union::Float(f1, ..), Union::Int(n2, ..)) => match op_x {
|
||||||
|
PlusAssign => **f1 += *n2 as crate::FLOAT,
|
||||||
|
MinusAssign => **f1 -= *n2 as crate::FLOAT,
|
||||||
|
MultiplyAssign => **f1 *= *n2 as crate::FLOAT,
|
||||||
|
DivideAssign => **f1 /= *n2 as crate::FLOAT,
|
||||||
|
ModuloAssign => **f1 %= *n2 as crate::FLOAT,
|
||||||
|
_ => done = false,
|
||||||
|
},
|
||||||
|
_ => done = false,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !done {
|
||||||
|
if let Some((func, need_context)) =
|
||||||
|
get_builtin_op_assignment_fn(op_x, &*lock_guard, &new_val)
|
||||||
|
{
|
||||||
|
// We may not need to bump the level because built-in's do not need it.
|
||||||
|
//auto_restore! { let orig_level = global.level; global.level += 1 }
|
||||||
|
|
||||||
|
let args = &mut [&mut *lock_guard, &mut new_val];
|
||||||
|
let context = need_context
|
||||||
|
.then(|| (self, op_x_str, global.source(), &*global, pos).into());
|
||||||
|
let _ = func(context, args).map_err(|err| err.fill_position(pos))?;
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !done {
|
||||||
|
let opx = Some(op_x);
|
||||||
let args = &mut [&mut *lock_guard, &mut new_val];
|
let args = &mut [&mut *lock_guard, &mut new_val];
|
||||||
|
|
||||||
if self.fast_operators() {
|
match self
|
||||||
if let Some((func, need_context)) =
|
.exec_native_fn_call(global, caches, op_x_str, opx, hash_x, args, true, pos)
|
||||||
get_builtin_op_assignment_fn(op_assign, args[0], args[1])
|
|
||||||
{
|
|
||||||
// Built-in found
|
|
||||||
auto_restore! { let orig_level = global.level; global.level += 1 }
|
|
||||||
|
|
||||||
let context = if need_context {
|
|
||||||
let op = op_assign.literal_syntax();
|
|
||||||
let source = global.source();
|
|
||||||
Some((self, op, source, &*global, pos).into())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
return func(context, args).map(|_| ());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let token = Some(op_assign);
|
|
||||||
let op_assign = op_assign.literal_syntax();
|
|
||||||
|
|
||||||
match self.exec_native_fn_call(global, caches, op_assign, token, hash1, args, true, pos)
|
|
||||||
{
|
{
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(err) if matches!(*err, ERR::ErrorFunctionNotFound(ref f, ..) if f.starts_with(op_assign)) =>
|
Err(err) if matches!(*err, ERR::ErrorFunctionNotFound(ref f, ..) if f.starts_with(op_x_str)) =>
|
||||||
{
|
{
|
||||||
// Expand to `var = var op rhs`
|
// Expand to `var = var op rhs`
|
||||||
let token = Some(op);
|
let op = Some(op);
|
||||||
let op = op.literal_syntax();
|
|
||||||
|
|
||||||
*args[0] = self
|
*args[0] = self
|
||||||
.exec_native_fn_call(global, caches, op, token, hash2, args, true, pos)?
|
.exec_native_fn_call(global, caches, op_str, op, hash, args, true, pos)?
|
||||||
.0;
|
.0;
|
||||||
}
|
}
|
||||||
Err(err) => return Err(err),
|
Err(err) => return Err(err),
|
||||||
}
|
}
|
||||||
|
|
||||||
self.check_data_size(&*args[0], root.position())?;
|
self.check_data_size(&*args[0], root.position())?;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Normal assignment
|
// Normal assignment
|
||||||
match target {
|
match target {
|
||||||
@ -200,24 +273,20 @@ impl Engine {
|
|||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset));
|
auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset));
|
||||||
|
|
||||||
|
self.track_operation(global, stmt.position())?;
|
||||||
|
|
||||||
// Coded this way for better branch prediction.
|
// Coded this way for better branch prediction.
|
||||||
// Popular branches are lifted out of the `match` statement into their own branches.
|
// Popular branches are lifted out of the `match` statement into their own branches.
|
||||||
|
|
||||||
// Function calls should account for a relatively larger portion of statements.
|
// Function calls should account for a relatively larger portion of statements.
|
||||||
if let Stmt::FnCall(x, pos) = stmt {
|
if let Stmt::FnCall(x, pos) = stmt {
|
||||||
self.track_operation(global, stmt.position())?;
|
|
||||||
|
|
||||||
return self.eval_fn_call_expr(global, caches, scope, this_ptr, x, *pos);
|
return self.eval_fn_call_expr(global, caches, scope, this_ptr, x, *pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then assignments.
|
// Then assignments.
|
||||||
// We shouldn't do this for too many variants because, soon or later, the added comparisons
|
|
||||||
// will cost more than the mis-predicted `match` branch.
|
|
||||||
if let Stmt::Assignment(x, ..) = stmt {
|
if let Stmt::Assignment(x, ..) = stmt {
|
||||||
let (op_info, BinaryExpr { lhs, rhs }) = &**x;
|
let (op_info, BinaryExpr { lhs, rhs }) = &**x;
|
||||||
|
|
||||||
self.track_operation(global, stmt.position())?;
|
|
||||||
|
|
||||||
if let Expr::Variable(x, ..) = lhs {
|
if let Expr::Variable(x, ..) = lhs {
|
||||||
let rhs_val = self
|
let rhs_val = self
|
||||||
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), rhs)?
|
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), rhs)?
|
||||||
@ -281,7 +350,97 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.track_operation(global, stmt.position())?;
|
// Then variable definitions.
|
||||||
|
if let Stmt::Var(x, options, pos) = stmt {
|
||||||
|
if !self.allow_shadowing() && scope.contains(&x.0) {
|
||||||
|
return Err(ERR::ErrorVariableExists(x.0.to_string(), *pos).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let/const statement
|
||||||
|
let (var_name, expr, index) = &**x;
|
||||||
|
|
||||||
|
let access = if options.contains(ASTFlags::CONSTANT) {
|
||||||
|
AccessMode::ReadOnly
|
||||||
|
} else {
|
||||||
|
AccessMode::ReadWrite
|
||||||
|
};
|
||||||
|
let export = options.contains(ASTFlags::EXPORTED);
|
||||||
|
|
||||||
|
// Check variable definition filter
|
||||||
|
if let Some(ref filter) = self.def_var_filter {
|
||||||
|
let will_shadow = scope.contains(var_name);
|
||||||
|
let is_const = access == AccessMode::ReadOnly;
|
||||||
|
let info = VarDefInfo {
|
||||||
|
name: var_name,
|
||||||
|
is_const,
|
||||||
|
nesting_level: global.scope_level,
|
||||||
|
will_shadow,
|
||||||
|
};
|
||||||
|
let orig_scope_len = scope.len();
|
||||||
|
let context =
|
||||||
|
EvalContext::new(self, global, caches, scope, this_ptr.as_deref_mut());
|
||||||
|
let filter_result = filter(true, info, context);
|
||||||
|
|
||||||
|
if orig_scope_len != scope.len() {
|
||||||
|
// The scope is changed, always search from now on
|
||||||
|
global.always_search_scope = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !filter_result? {
|
||||||
|
return Err(ERR::ErrorForbiddenVariable(var_name.to_string(), *pos).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate initial value
|
||||||
|
let mut value = self
|
||||||
|
.eval_expr(global, caches, scope, this_ptr, expr)?
|
||||||
|
.flatten()
|
||||||
|
.intern_string(self);
|
||||||
|
|
||||||
|
let _alias = if !rewind_scope {
|
||||||
|
// Put global constants into global module
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
if global.scope_level == 0
|
||||||
|
&& access == AccessMode::ReadOnly
|
||||||
|
&& global.lib.iter().any(|m| !m.is_empty())
|
||||||
|
{
|
||||||
|
crate::func::locked_write(global.constants.get_or_insert_with(|| {
|
||||||
|
crate::Shared::new(crate::Locked::new(std::collections::BTreeMap::new()))
|
||||||
|
}))
|
||||||
|
.insert(var_name.name.clone(), value.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if export {
|
||||||
|
Some(var_name)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else if export {
|
||||||
|
unreachable!("exported variable not on global level");
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(index) = index {
|
||||||
|
value.set_access_mode(access);
|
||||||
|
*scope.get_mut_by_index(scope.len() - index.get()) = value;
|
||||||
|
} else {
|
||||||
|
scope.push_entry(var_name.name.clone(), access, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
if let Some(alias) = _alias {
|
||||||
|
scope.add_alias_by_index(scope.len() - 1, alias.as_str().into());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(Dynamic::UNIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop merging branches here!
|
||||||
|
// We shouldn't lift out too many variants because, soon or later, the added comparisons
|
||||||
|
// will cost more than the mis-predicted `match` branch.
|
||||||
|
Self::black_box();
|
||||||
|
|
||||||
match stmt {
|
match stmt {
|
||||||
// No-op
|
// No-op
|
||||||
@ -293,10 +452,13 @@ impl Engine {
|
|||||||
.map(Dynamic::flatten),
|
.map(Dynamic::flatten),
|
||||||
|
|
||||||
// Block scope
|
// Block scope
|
||||||
Stmt::Block(statements, ..) if statements.is_empty() => Ok(Dynamic::UNIT),
|
|
||||||
Stmt::Block(statements, ..) => {
|
Stmt::Block(statements, ..) => {
|
||||||
|
if statements.is_empty() {
|
||||||
|
Ok(Dynamic::UNIT)
|
||||||
|
} else {
|
||||||
self.eval_stmt_block(global, caches, scope, this_ptr, statements, true)
|
self.eval_stmt_block(global, caches, scope, this_ptr, statements, true)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If statement
|
// If statement
|
||||||
Stmt::If(x, ..) => {
|
Stmt::If(x, ..) => {
|
||||||
@ -311,12 +473,14 @@ impl Engine {
|
|||||||
.as_bool()
|
.as_bool()
|
||||||
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, expr.position()))?;
|
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, expr.position()))?;
|
||||||
|
|
||||||
if guard_val && !if_block.is_empty() {
|
match guard_val {
|
||||||
|
true if !if_block.is_empty() => {
|
||||||
self.eval_stmt_block(global, caches, scope, this_ptr, if_block, true)
|
self.eval_stmt_block(global, caches, scope, this_ptr, if_block, true)
|
||||||
} else if !guard_val && !else_block.is_empty() {
|
}
|
||||||
|
false if !else_block.is_empty() => {
|
||||||
self.eval_stmt_block(global, caches, scope, this_ptr, else_block, true)
|
self.eval_stmt_block(global, caches, scope, this_ptr, else_block, true)
|
||||||
} else {
|
}
|
||||||
Ok(Dynamic::UNIT)
|
_ => Ok(Dynamic::UNIT),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,7 +507,7 @@ impl Engine {
|
|||||||
|
|
||||||
// First check hashes
|
// First check hashes
|
||||||
if let Some(case_blocks_list) = cases.get(&hash) {
|
if let Some(case_blocks_list) = cases.get(&hash) {
|
||||||
assert!(!case_blocks_list.is_empty());
|
debug_assert!(!case_blocks_list.is_empty());
|
||||||
|
|
||||||
for &index in case_blocks_list {
|
for &index in case_blocks_list {
|
||||||
let block = &expressions[index];
|
let block = &expressions[index];
|
||||||
@ -363,15 +527,13 @@ impl Engine {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if value.is_int() && !ranges.is_empty() {
|
} else if !ranges.is_empty() {
|
||||||
// Then check integer ranges
|
// Then check integer ranges
|
||||||
let value = value.as_int().expect("`INT`");
|
for r in ranges.iter().filter(|r| r.contains(&value)) {
|
||||||
|
let ConditionalExpr { condition, expr } = &expressions[r.index()];
|
||||||
|
|
||||||
for r in ranges.iter().filter(|r| r.contains(value)) {
|
let cond_result = match condition {
|
||||||
let block = &expressions[r.index()];
|
Expr::BoolConstant(b, ..) => *b,
|
||||||
|
|
||||||
let cond_result = match block.condition {
|
|
||||||
Expr::BoolConstant(b, ..) => b,
|
|
||||||
ref c => self
|
ref c => self
|
||||||
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), c)?
|
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), c)?
|
||||||
.as_bool()
|
.as_bool()
|
||||||
@ -381,7 +543,7 @@ impl Engine {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if cond_result {
|
if cond_result {
|
||||||
result = Some(&block.expr);
|
result = Some(expr);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -521,12 +683,10 @@ impl Engine {
|
|||||||
auto_restore! { scope => rewind; let orig_scope_len = scope.len(); }
|
auto_restore! { scope => rewind; let orig_scope_len = scope.len(); }
|
||||||
|
|
||||||
// Add the loop variables
|
// Add the loop variables
|
||||||
let counter_index = if counter.is_empty() {
|
let counter_index = (!counter.is_empty()).then(|| {
|
||||||
usize::MAX
|
|
||||||
} else {
|
|
||||||
scope.push(counter.name.clone(), 0 as INT);
|
scope.push(counter.name.clone(), 0 as INT);
|
||||||
scope.len() - 1
|
scope.len() - 1
|
||||||
};
|
});
|
||||||
|
|
||||||
scope.push(var_name.name.clone(), ());
|
scope.push(var_name.name.clone(), ());
|
||||||
let index = scope.len() - 1;
|
let index = scope.len() - 1;
|
||||||
@ -535,7 +695,7 @@ impl Engine {
|
|||||||
|
|
||||||
for (x, iter_value) in iter_func(iter_obj).enumerate() {
|
for (x, iter_value) in iter_func(iter_obj).enumerate() {
|
||||||
// Increment counter
|
// Increment counter
|
||||||
if counter_index < usize::MAX {
|
if let Some(counter_index) = counter_index {
|
||||||
// As the variable increments from 0, this should always work
|
// As the variable increments from 0, this should always work
|
||||||
// since any overflow will first be caught below.
|
// since any overflow will first be caught below.
|
||||||
let index_value = x as INT;
|
let index_value = x as INT;
|
||||||
@ -695,94 +855,6 @@ impl Engine {
|
|||||||
// Empty return
|
// Empty return
|
||||||
Stmt::Return(None, .., pos) => Err(ERR::Return(Dynamic::UNIT, *pos).into()),
|
Stmt::Return(None, .., pos) => Err(ERR::Return(Dynamic::UNIT, *pos).into()),
|
||||||
|
|
||||||
// Let/const statement - shadowing disallowed
|
|
||||||
Stmt::Var(x, .., pos) if !self.allow_shadowing() && scope.contains(&x.0) => {
|
|
||||||
Err(ERR::ErrorVariableExists(x.0.to_string(), *pos).into())
|
|
||||||
}
|
|
||||||
// Let/const statement
|
|
||||||
Stmt::Var(x, options, pos) => {
|
|
||||||
let (var_name, expr, index) = &**x;
|
|
||||||
|
|
||||||
let access = if options.contains(ASTFlags::CONSTANT) {
|
|
||||||
AccessMode::ReadOnly
|
|
||||||
} else {
|
|
||||||
AccessMode::ReadWrite
|
|
||||||
};
|
|
||||||
let export = options.contains(ASTFlags::EXPORTED);
|
|
||||||
|
|
||||||
// Check variable definition filter
|
|
||||||
if let Some(ref filter) = self.def_var_filter {
|
|
||||||
let will_shadow = scope.contains(var_name);
|
|
||||||
let is_const = access == AccessMode::ReadOnly;
|
|
||||||
let info = VarDefInfo {
|
|
||||||
name: var_name,
|
|
||||||
is_const,
|
|
||||||
nesting_level: global.scope_level,
|
|
||||||
will_shadow,
|
|
||||||
};
|
|
||||||
let orig_scope_len = scope.len();
|
|
||||||
let context =
|
|
||||||
EvalContext::new(self, global, caches, scope, this_ptr.as_deref_mut());
|
|
||||||
let filter_result = filter(true, info, context);
|
|
||||||
|
|
||||||
if orig_scope_len != scope.len() {
|
|
||||||
// The scope is changed, always search from now on
|
|
||||||
global.always_search_scope = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !filter_result? {
|
|
||||||
return Err(ERR::ErrorForbiddenVariable(var_name.to_string(), *pos).into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluate initial value
|
|
||||||
let mut value = self
|
|
||||||
.eval_expr(global, caches, scope, this_ptr, expr)?
|
|
||||||
.flatten()
|
|
||||||
.intern_string(self);
|
|
||||||
|
|
||||||
let _alias = if !rewind_scope {
|
|
||||||
// Put global constants into global module
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
if global.scope_level == 0
|
|
||||||
&& access == AccessMode::ReadOnly
|
|
||||||
&& global.lib.iter().any(|m| !m.is_empty())
|
|
||||||
{
|
|
||||||
crate::func::locked_write(global.constants.get_or_insert_with(|| {
|
|
||||||
crate::Shared::new(
|
|
||||||
crate::Locked::new(std::collections::BTreeMap::new()),
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
.insert(var_name.name.clone(), value.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
if export {
|
|
||||||
Some(var_name)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else if export {
|
|
||||||
unreachable!("exported variable not on global level");
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(index) = index {
|
|
||||||
value.set_access_mode(access);
|
|
||||||
*scope.get_mut_by_index(scope.len() - index.get()) = value;
|
|
||||||
} else {
|
|
||||||
scope.push_entry(var_name.name.clone(), access, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
if let Some(alias) = _alias {
|
|
||||||
scope.add_alias_by_index(scope.len() - 1, alias.as_str().into());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Dynamic::UNIT)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Import statement
|
// Import statement
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Import(x, _pos) => {
|
Stmt::Import(x, _pos) => {
|
||||||
@ -807,14 +879,16 @@ impl Engine {
|
|||||||
|
|
||||||
let module = resolver
|
let module = resolver
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|r| match r.resolve_raw(self, global, &path, path_pos) {
|
.and_then(
|
||||||
|
|r| match r.resolve_raw(self, global, scope, &path, path_pos) {
|
||||||
Err(err) if matches!(*err, ERR::ErrorModuleNotFound(..)) => None,
|
Err(err) if matches!(*err, ERR::ErrorModuleNotFound(..)) => None,
|
||||||
result => Some(result),
|
result => Some(result),
|
||||||
})
|
},
|
||||||
|
)
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
Some(
|
Some(
|
||||||
self.module_resolver
|
self.module_resolver()
|
||||||
.resolve_raw(self, global, &path, path_pos),
|
.resolve_raw(self, global, scope, &path, path_pos),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
|
@ -27,48 +27,6 @@ use rust_decimal::Decimal;
|
|||||||
/// The `unchecked` feature is not active.
|
/// The `unchecked` feature is not active.
|
||||||
const CHECKED_BUILD: bool = cfg!(not(feature = "unchecked"));
|
const CHECKED_BUILD: bool = cfg!(not(feature = "unchecked"));
|
||||||
|
|
||||||
/// Is the type a numeric type?
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
fn is_numeric(type_id: TypeId) -> bool {
|
|
||||||
if type_id == TypeId::of::<INT>() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "only_i64"))]
|
|
||||||
#[cfg(not(feature = "only_i32"))]
|
|
||||||
if type_id == TypeId::of::<u8>()
|
|
||||||
|| type_id == TypeId::of::<u16>()
|
|
||||||
|| type_id == TypeId::of::<u32>()
|
|
||||||
|| type_id == TypeId::of::<u64>()
|
|
||||||
|| type_id == TypeId::of::<i8>()
|
|
||||||
|| type_id == TypeId::of::<i16>()
|
|
||||||
|| type_id == TypeId::of::<i32>()
|
|
||||||
|| type_id == TypeId::of::<i64>()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "only_i64"))]
|
|
||||||
#[cfg(not(feature = "only_i32"))]
|
|
||||||
#[cfg(not(target_family = "wasm"))]
|
|
||||||
if type_id == TypeId::of::<u128>() || type_id == TypeId::of::<i128>() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
|
||||||
if type_id == TypeId::of::<f32>() || type_id == TypeId::of::<f64>() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "decimal")]
|
|
||||||
if type_id == TypeId::of::<rust_decimal::Decimal>() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A function that returns `true`.
|
/// A function that returns `true`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[allow(clippy::unnecessary_wraps)]
|
#[allow(clippy::unnecessary_wraps)]
|
||||||
@ -584,22 +542,7 @@ pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option<
|
|||||||
|
|
||||||
// One of the operands is a custom type, so it is never built-in
|
// One of the operands is a custom type, so it is never built-in
|
||||||
if x.is_variant() || y.is_variant() {
|
if x.is_variant() || y.is_variant() {
|
||||||
return if is_numeric(type1) && is_numeric(type2) {
|
return None;
|
||||||
// Disallow comparisons between different numeric types
|
|
||||||
None
|
|
||||||
} else if type1 != type2 {
|
|
||||||
// If the types are not the same, default to not compare
|
|
||||||
match op {
|
|
||||||
NotEqualsTo => Some((const_true_fn, false)),
|
|
||||||
EqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => {
|
|
||||||
Some((const_false_fn, false))
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Disallow comparisons between the same type
|
|
||||||
None
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default comparison operators for different types
|
// Default comparison operators for different types
|
||||||
@ -741,6 +684,7 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt
|
|||||||
return match op {
|
return match op {
|
||||||
AndAssign => impl_op!(bool = x && as_bool),
|
AndAssign => impl_op!(bool = x && as_bool),
|
||||||
OrAssign => impl_op!(bool = x || as_bool),
|
OrAssign => impl_op!(bool = x || as_bool),
|
||||||
|
XOrAssign => impl_op!(bool = x ^ as_bool),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
204
src/func/call.rs
204
src/func/call.rs
@ -9,6 +9,7 @@ use crate::engine::{
|
|||||||
};
|
};
|
||||||
use crate::eval::{Caches, FnResolutionCacheEntry, GlobalRuntimeState};
|
use crate::eval::{Caches, FnResolutionCacheEntry, GlobalRuntimeState};
|
||||||
use crate::tokenizer::{is_valid_function_name, Token};
|
use crate::tokenizer::{is_valid_function_name, Token};
|
||||||
|
use crate::types::dynamic::Union;
|
||||||
use crate::{
|
use crate::{
|
||||||
calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnArgsVec, FnPtr, ImmutableString,
|
calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnArgsVec, FnPtr, ImmutableString,
|
||||||
OptimizationLevel, Position, RhaiResult, RhaiResultOf, Scope, Shared, ERR,
|
OptimizationLevel, Position, RhaiResult, RhaiResultOf, Scope, Shared, ERR,
|
||||||
@ -363,7 +364,7 @@ impl Engine {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if let Some(FnResolutionCacheEntry { func, source }) = func {
|
if let Some(FnResolutionCacheEntry { func, source }) = func {
|
||||||
assert!(func.is_native());
|
debug_assert!(func.is_native());
|
||||||
|
|
||||||
// Push a new call stack frame
|
// Push a new call stack frame
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
@ -398,11 +399,9 @@ impl Engine {
|
|||||||
let is_method = func.is_method();
|
let is_method = func.is_method();
|
||||||
let src = source.as_ref().map(|s| s.as_str());
|
let src = source.as_ref().map(|s| s.as_str());
|
||||||
|
|
||||||
let context = if func.has_context() {
|
let context = func
|
||||||
Some((self, name, src, &*global, pos).into())
|
.has_context()
|
||||||
} else {
|
.then(|| (self, name, src, &*global, pos).into());
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut _result = if let Some(f) = func.get_plugin_fn() {
|
let mut _result = if let Some(f) = func.get_plugin_fn() {
|
||||||
if !f.is_pure() && !args.is_empty() && args[0].is_read_only() {
|
if !f.is_pure() && !args.is_empty() && args[0].is_read_only() {
|
||||||
@ -461,18 +460,26 @@ impl Engine {
|
|||||||
// See if the function match print/debug (which requires special processing)
|
// See if the function match print/debug (which requires special processing)
|
||||||
return Ok(match name {
|
return Ok(match name {
|
||||||
KEYWORD_PRINT => {
|
KEYWORD_PRINT => {
|
||||||
|
if let Some(ref print) = self.print {
|
||||||
let text = result.into_immutable_string().map_err(|typ| {
|
let text = result.into_immutable_string().map_err(|typ| {
|
||||||
let t = self.map_type_name(type_name::<ImmutableString>()).into();
|
let t = self.map_type_name(type_name::<ImmutableString>()).into();
|
||||||
ERR::ErrorMismatchOutputType(t, typ.into(), pos)
|
ERR::ErrorMismatchOutputType(t, typ.into(), pos)
|
||||||
})?;
|
})?;
|
||||||
((*self.print)(&text).into(), false)
|
(print(&text).into(), false)
|
||||||
|
} else {
|
||||||
|
(Dynamic::UNIT, false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
KEYWORD_DEBUG => {
|
KEYWORD_DEBUG => {
|
||||||
|
if let Some(ref debug) = self.debug {
|
||||||
let text = result.into_immutable_string().map_err(|typ| {
|
let text = result.into_immutable_string().map_err(|typ| {
|
||||||
let t = self.map_type_name(type_name::<ImmutableString>()).into();
|
let t = self.map_type_name(type_name::<ImmutableString>()).into();
|
||||||
ERR::ErrorMismatchOutputType(t, typ.into(), pos)
|
ERR::ErrorMismatchOutputType(t, typ.into(), pos)
|
||||||
})?;
|
})?;
|
||||||
((*self.debug)(&text, global.source(), pos).into(), false)
|
(debug(&text, global.source(), pos).into(), false)
|
||||||
|
} else {
|
||||||
|
(Dynamic::UNIT, false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => (result, is_method),
|
_ => (result, is_method),
|
||||||
});
|
});
|
||||||
@ -633,7 +640,7 @@ impl Engine {
|
|||||||
.cloned()
|
.cloned()
|
||||||
{
|
{
|
||||||
// Script function call
|
// Script function call
|
||||||
assert!(func.is_script());
|
debug_assert!(func.is_script());
|
||||||
|
|
||||||
let f = func.get_script_fn_def().expect("script-defined function");
|
let f = func.get_script_fn_def().expect("script-defined function");
|
||||||
let environ = func.get_encapsulated_environ();
|
let environ = func.get_encapsulated_environ();
|
||||||
@ -810,7 +817,8 @@ impl Engine {
|
|||||||
if call_args.is_empty() {
|
if call_args.is_empty() {
|
||||||
let typ = self.map_type_name(target.type_name());
|
let typ = self.map_type_name(target.type_name());
|
||||||
return Err(self.make_type_mismatch_err::<FnPtr>(typ, fn_call_pos));
|
return Err(self.make_type_mismatch_err::<FnPtr>(typ, fn_call_pos));
|
||||||
} else if !call_args[0].is_fnptr() {
|
}
|
||||||
|
if !call_args[0].is_fnptr() {
|
||||||
let typ = self.map_type_name(call_args[0].type_name());
|
let typ = self.map_type_name(call_args[0].type_name());
|
||||||
return Err(self.make_type_mismatch_err::<FnPtr>(typ, first_arg_pos));
|
return Err(self.make_type_mismatch_err::<FnPtr>(typ, first_arg_pos));
|
||||||
}
|
}
|
||||||
@ -1251,9 +1259,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call with blank scope
|
// Call with blank scope
|
||||||
if total_args == 0 && curry.is_empty() {
|
if total_args > 0 || !curry.is_empty() {
|
||||||
// No arguments
|
|
||||||
} else {
|
|
||||||
// If the first argument is a variable, and there is no curried arguments,
|
// If the first argument is a variable, and there is no curried arguments,
|
||||||
// convert to method-call style in order to leverage potential &mut first argument and
|
// convert to method-call style in order to leverage potential &mut first argument and
|
||||||
// avoid cloning the value
|
// avoid cloning the value
|
||||||
@ -1324,9 +1330,7 @@ impl Engine {
|
|||||||
let args = &mut FnArgsVec::with_capacity(args_expr.len());
|
let args = &mut FnArgsVec::with_capacity(args_expr.len());
|
||||||
let mut first_arg_value = None;
|
let mut first_arg_value = None;
|
||||||
|
|
||||||
if args_expr.is_empty() {
|
if !args_expr.is_empty() {
|
||||||
// No arguments
|
|
||||||
} else {
|
|
||||||
// See if the first argument is a variable (not namespace-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
|
// If so, convert to method-call style in order to leverage potential &mut first argument
|
||||||
// and avoid cloning the value
|
// and avoid cloning the value
|
||||||
@ -1460,11 +1464,9 @@ impl Engine {
|
|||||||
|
|
||||||
Some(f) if f.is_plugin_fn() => {
|
Some(f) if f.is_plugin_fn() => {
|
||||||
let f = f.get_plugin_fn().expect("plugin function");
|
let f = f.get_plugin_fn().expect("plugin function");
|
||||||
let context = if f.has_context() {
|
let context = f
|
||||||
Some((self, fn_name, module.id(), &*global, pos).into())
|
.has_context()
|
||||||
} else {
|
.then(|| (self, fn_name, module.id(), &*global, pos).into());
|
||||||
None
|
|
||||||
};
|
|
||||||
if !f.is_pure() && !args.is_empty() && args[0].is_read_only() {
|
if !f.is_pure() && !args.is_empty() && args[0].is_read_only() {
|
||||||
Err(ERR::ErrorNonPureMethodCallOnConstant(fn_name.to_string(), pos).into())
|
Err(ERR::ErrorNonPureMethodCallOnConstant(fn_name.to_string(), pos).into())
|
||||||
} else {
|
} else {
|
||||||
@ -1475,29 +1477,27 @@ impl Engine {
|
|||||||
|
|
||||||
Some(f) if f.is_native() => {
|
Some(f) if f.is_native() => {
|
||||||
let func = f.get_native_fn().expect("native function");
|
let func = f.get_native_fn().expect("native function");
|
||||||
let context = if f.has_context() {
|
let context = f
|
||||||
Some((self, fn_name, module.id(), &*global, pos).into())
|
.has_context()
|
||||||
} else {
|
.then(|| (self, fn_name, module.id(), &*global, pos).into());
|
||||||
None
|
|
||||||
};
|
|
||||||
func(context, args).and_then(|r| self.check_data_size(r, pos))
|
func(context, args).and_then(|r| self.check_data_size(r, pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(f) => unreachable!("unknown function type: {:?}", f),
|
Some(f) => unreachable!("unknown function type: {:?}", f),
|
||||||
|
|
||||||
None => {
|
None => Err(ERR::ErrorFunctionNotFound(
|
||||||
let sig = if namespace.is_empty() {
|
if namespace.is_empty() {
|
||||||
self.gen_fn_call_signature(fn_name, args)
|
self.gen_fn_call_signature(fn_name, args)
|
||||||
} else {
|
} else {
|
||||||
format!(
|
format!(
|
||||||
"{namespace}{}{}",
|
"{namespace}{}{}",
|
||||||
crate::tokenizer::Token::DoubleColon.literal_syntax(),
|
crate::engine::NAMESPACE_SEPARATOR,
|
||||||
self.gen_fn_call_signature(fn_name, args)
|
self.gen_fn_call_signature(fn_name, args)
|
||||||
)
|
)
|
||||||
};
|
},
|
||||||
|
pos,
|
||||||
Err(ERR::ErrorFunctionNotFound(sig, pos).into())
|
)
|
||||||
}
|
.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1547,6 +1547,9 @@ impl Engine {
|
|||||||
/// # Main Entry-Point (`FnCallExpr`)
|
/// # Main Entry-Point (`FnCallExpr`)
|
||||||
///
|
///
|
||||||
/// Evaluate a function call expression.
|
/// Evaluate a function call expression.
|
||||||
|
///
|
||||||
|
/// This method tries to short-circuit function resolution under Fast Operators mode if the
|
||||||
|
/// function call is an operator.
|
||||||
pub(crate) fn eval_fn_call_expr(
|
pub(crate) fn eval_fn_call_expr(
|
||||||
&self,
|
&self,
|
||||||
global: &mut GlobalRuntimeState,
|
global: &mut GlobalRuntimeState,
|
||||||
@ -1570,23 +1573,29 @@ impl Engine {
|
|||||||
let op_token = op_token.as_ref();
|
let op_token = op_token.as_ref();
|
||||||
|
|
||||||
// Short-circuit native unary operator call if under Fast Operators mode
|
// Short-circuit native unary operator call if under Fast Operators mode
|
||||||
if op_token == Some(&Token::Bang) && self.fast_operators() && args.len() == 1 {
|
if self.fast_operators() && args.len() == 1 && op_token == Some(&Token::Bang) {
|
||||||
let mut value = self
|
let mut value = self
|
||||||
.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), &args[0])?
|
.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), &args[0])?
|
||||||
.0
|
.0
|
||||||
.flatten();
|
.flatten();
|
||||||
|
|
||||||
return value.as_bool().map(|r| (!r).into()).or_else(|_| {
|
return match value.0 {
|
||||||
|
Union::Bool(b, ..) => Ok((!b).into()),
|
||||||
|
_ => {
|
||||||
let operand = &mut [&mut value];
|
let operand = &mut [&mut value];
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
global, caches, None, name, op_token, *hashes, operand, false, false, pos,
|
global, caches, None, name, op_token, *hashes, operand, false, false, pos,
|
||||||
)
|
)
|
||||||
.map(|(v, ..)| v)
|
.map(|(v, ..)| v)
|
||||||
});
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Short-circuit native binary operator call if under Fast Operators mode
|
// Short-circuit native binary operator call if under Fast Operators mode
|
||||||
if op_token.is_some() && self.fast_operators() && args.len() == 2 {
|
if op_token.is_some() && self.fast_operators() && args.len() == 2 {
|
||||||
|
#[allow(clippy::wildcard_imports)]
|
||||||
|
use Token::*;
|
||||||
|
|
||||||
let mut lhs = self
|
let mut lhs = self
|
||||||
.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), &args[0])?
|
.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), &args[0])?
|
||||||
.0
|
.0
|
||||||
@ -1597,19 +1606,128 @@ impl Engine {
|
|||||||
.0
|
.0
|
||||||
.flatten();
|
.flatten();
|
||||||
|
|
||||||
|
// For extremely simple primary data operations, do it directly
|
||||||
|
// to avoid the overhead of calling a function.
|
||||||
|
match (&lhs.0, &rhs.0) {
|
||||||
|
(Union::Unit(..), Union::Unit(..)) => match op_token.unwrap() {
|
||||||
|
EqualsTo => return Ok(Dynamic::TRUE),
|
||||||
|
NotEqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan
|
||||||
|
| LessThanEqualsTo => return Ok(Dynamic::FALSE),
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
(Union::Bool(b1, ..), Union::Bool(b2, ..)) => match op_token.unwrap() {
|
||||||
|
EqualsTo => return Ok((*b1 == *b2).into()),
|
||||||
|
NotEqualsTo => return Ok((*b1 != *b2).into()),
|
||||||
|
GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => {
|
||||||
|
return Ok(Dynamic::FALSE)
|
||||||
|
}
|
||||||
|
Pipe => return Ok((*b1 || *b2).into()),
|
||||||
|
Ampersand => return Ok((*b1 && *b2).into()),
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
(Union::Int(n1, ..), Union::Int(n2, ..)) => {
|
||||||
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
#[allow(clippy::wildcard_imports)]
|
||||||
|
use crate::packages::arithmetic::arith_basic::INT::functions::*;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
match op_token.unwrap() {
|
||||||
|
EqualsTo => return Ok((*n1 == *n2).into()),
|
||||||
|
NotEqualsTo => return Ok((*n1 != *n2).into()),
|
||||||
|
GreaterThan => return Ok((*n1 > *n2).into()),
|
||||||
|
GreaterThanEqualsTo => return Ok((*n1 >= *n2).into()),
|
||||||
|
LessThan => return Ok((*n1 < *n2).into()),
|
||||||
|
LessThanEqualsTo => return Ok((*n1 <= *n2).into()),
|
||||||
|
Plus => return add(*n1, *n2).map(Into::into),
|
||||||
|
Minus => return subtract(*n1, *n2).map(Into::into),
|
||||||
|
Multiply => return multiply(*n1, *n2).map(Into::into),
|
||||||
|
Divide => return divide(*n1, *n2).map(Into::into),
|
||||||
|
Modulo => return modulo(*n1, *n2).map(Into::into),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
#[cfg(feature = "unchecked")]
|
||||||
|
match op_token.unwrap() {
|
||||||
|
EqualsTo => return Ok((*n1 == *n2).into()),
|
||||||
|
NotEqualsTo => return Ok((*n1 != *n2).into()),
|
||||||
|
GreaterThan => return Ok((*n1 > *n2).into()),
|
||||||
|
GreaterThanEqualsTo => return Ok((*n1 >= *n2).into()),
|
||||||
|
LessThan => return Ok((*n1 < *n2).into()),
|
||||||
|
LessThanEqualsTo => return Ok((*n1 <= *n2).into()),
|
||||||
|
Plus => return Ok((*n1 + *n2).into()),
|
||||||
|
Minus => return Ok((*n1 - *n2).into()),
|
||||||
|
Multiply => return Ok((*n1 * *n2).into()),
|
||||||
|
Divide => return Ok((*n1 / *n2).into()),
|
||||||
|
Modulo => return Ok((*n1 % *n2).into()),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
(Union::Float(f1, ..), Union::Float(f2, ..)) => match op_token.unwrap() {
|
||||||
|
EqualsTo => return Ok((**f1 == **f2).into()),
|
||||||
|
NotEqualsTo => return Ok((**f1 != **f2).into()),
|
||||||
|
GreaterThan => return Ok((**f1 > **f2).into()),
|
||||||
|
GreaterThanEqualsTo => return Ok((**f1 >= **f2).into()),
|
||||||
|
LessThan => return Ok((**f1 < **f2).into()),
|
||||||
|
LessThanEqualsTo => return Ok((**f1 <= **f2).into()),
|
||||||
|
Plus => return Ok((**f1 + **f2).into()),
|
||||||
|
Minus => return Ok((**f1 - **f2).into()),
|
||||||
|
Multiply => return Ok((**f1 * **f2).into()),
|
||||||
|
Divide => return Ok((**f1 / **f2).into()),
|
||||||
|
Modulo => return Ok((**f1 % **f2).into()),
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
(Union::Float(f1, ..), Union::Int(n2, ..)) => match op_token.unwrap() {
|
||||||
|
EqualsTo => return Ok((**f1 == (*n2 as crate::FLOAT)).into()),
|
||||||
|
NotEqualsTo => return Ok((**f1 != (*n2 as crate::FLOAT)).into()),
|
||||||
|
GreaterThan => return Ok((**f1 > (*n2 as crate::FLOAT)).into()),
|
||||||
|
GreaterThanEqualsTo => return Ok((**f1 >= (*n2 as crate::FLOAT)).into()),
|
||||||
|
LessThan => return Ok((**f1 < (*n2 as crate::FLOAT)).into()),
|
||||||
|
LessThanEqualsTo => return Ok((**f1 <= (*n2 as crate::FLOAT)).into()),
|
||||||
|
Plus => return Ok((**f1 + (*n2 as crate::FLOAT)).into()),
|
||||||
|
Minus => return Ok((**f1 - (*n2 as crate::FLOAT)).into()),
|
||||||
|
Multiply => return Ok((**f1 * (*n2 as crate::FLOAT)).into()),
|
||||||
|
Divide => return Ok((**f1 / (*n2 as crate::FLOAT)).into()),
|
||||||
|
Modulo => return Ok((**f1 % (*n2 as crate::FLOAT)).into()),
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
(Union::Int(n1, ..), Union::Float(f2, ..)) => match op_token.unwrap() {
|
||||||
|
EqualsTo => return Ok(((*n1 as crate::FLOAT) == **f2).into()),
|
||||||
|
NotEqualsTo => return Ok(((*n1 as crate::FLOAT) != **f2).into()),
|
||||||
|
GreaterThan => return Ok(((*n1 as crate::FLOAT) > **f2).into()),
|
||||||
|
GreaterThanEqualsTo => return Ok(((*n1 as crate::FLOAT) >= **f2).into()),
|
||||||
|
LessThan => return Ok(((*n1 as crate::FLOAT) < **f2).into()),
|
||||||
|
LessThanEqualsTo => return Ok(((*n1 as crate::FLOAT) <= **f2).into()),
|
||||||
|
Plus => return Ok(((*n1 as crate::FLOAT) + **f2).into()),
|
||||||
|
Minus => return Ok(((*n1 as crate::FLOAT) - **f2).into()),
|
||||||
|
Multiply => return Ok(((*n1 as crate::FLOAT) * **f2).into()),
|
||||||
|
Divide => return Ok(((*n1 as crate::FLOAT) / **f2).into()),
|
||||||
|
Modulo => return Ok(((*n1 as crate::FLOAT) % **f2).into()),
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
(Union::Str(s1, ..), Union::Str(s2, ..)) => match op_token.unwrap() {
|
||||||
|
EqualsTo => return Ok((s1 == s2).into()),
|
||||||
|
NotEqualsTo => return Ok((s1 != s2).into()),
|
||||||
|
GreaterThan => return Ok((s1 > s2).into()),
|
||||||
|
GreaterThanEqualsTo => return Ok((s1 >= s2).into()),
|
||||||
|
LessThan => return Ok((s1 < s2).into()),
|
||||||
|
LessThanEqualsTo => return Ok((s1 <= s2).into()),
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
let operands = &mut [&mut lhs, &mut rhs];
|
let operands = &mut [&mut lhs, &mut rhs];
|
||||||
|
|
||||||
if let Some((func, need_context)) =
|
if let Some((func, need_context)) =
|
||||||
get_builtin_binary_op_fn(op_token.as_ref().unwrap(), operands[0], operands[1])
|
get_builtin_binary_op_fn(op_token.as_ref().unwrap(), operands[0], operands[1])
|
||||||
{
|
{
|
||||||
// Built-in found
|
// We may not need to bump the level because built-in's do not need it.
|
||||||
auto_restore! { let orig_level = global.level; global.level += 1 }
|
//auto_restore! { let orig_level = global.level; global.level += 1 }
|
||||||
|
|
||||||
let context = if need_context {
|
let context =
|
||||||
Some((self, name.as_str(), None, &*global, pos).into())
|
need_context.then(|| (self, name.as_str(), None, &*global, pos).into());
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
return func(context, operands);
|
return func(context, operands);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,8 @@ impl Hasher for StraightHasher {
|
|||||||
fn finish(&self) -> u64 {
|
fn finish(&self) -> u64 {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
#[inline(always)]
|
#[cold]
|
||||||
|
#[inline(never)]
|
||||||
fn write(&mut self, _bytes: &[u8]) {
|
fn write(&mut self, _bytes: &[u8]) {
|
||||||
panic!("StraightHasher can only hash u64 values");
|
panic!("StraightHasher can only hash u64 values");
|
||||||
}
|
}
|
||||||
@ -85,7 +86,7 @@ pub fn calc_var_hash<'a>(namespace: impl IntoIterator<Item = &'a str>, var_name:
|
|||||||
}
|
}
|
||||||
count += 1;
|
count += 1;
|
||||||
});
|
});
|
||||||
count.hash(s);
|
s.write_usize(count);
|
||||||
var_name.hash(s);
|
var_name.hash(s);
|
||||||
|
|
||||||
s.finish()
|
s.finish()
|
||||||
@ -119,9 +120,9 @@ pub fn calc_fn_hash<'a>(
|
|||||||
}
|
}
|
||||||
count += 1;
|
count += 1;
|
||||||
});
|
});
|
||||||
count.hash(s);
|
s.write_usize(count);
|
||||||
fn_name.hash(s);
|
fn_name.hash(s);
|
||||||
num.hash(s);
|
s.write_usize(num);
|
||||||
|
|
||||||
s.finish()
|
s.finish()
|
||||||
}
|
}
|
||||||
@ -133,13 +134,12 @@ pub fn calc_fn_hash<'a>(
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn calc_fn_hash_full(base: u64, params: impl IntoIterator<Item = TypeId>) -> u64 {
|
pub fn calc_fn_hash_full(base: u64, params: impl IntoIterator<Item = TypeId>) -> u64 {
|
||||||
let s = &mut get_hasher();
|
let s = &mut get_hasher();
|
||||||
base.hash(s);
|
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
params.into_iter().for_each(|t| {
|
params.into_iter().for_each(|t| {
|
||||||
t.hash(s);
|
t.hash(s);
|
||||||
count += 1;
|
count += 1;
|
||||||
});
|
});
|
||||||
count.hash(s);
|
s.write_usize(count);
|
||||||
|
|
||||||
s.finish()
|
s.finish() ^ base
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,7 @@ impl Engine {
|
|||||||
let orig_fn_resolution_caches_len = caches.fn_resolution_caches_len();
|
let orig_fn_resolution_caches_len = caches.fn_resolution_caches_len();
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
let orig_constants = if let Some(environ) = _environ {
|
let orig_constants = _environ.map(|environ| {
|
||||||
let EncapsulatedEnviron {
|
let EncapsulatedEnviron {
|
||||||
lib,
|
lib,
|
||||||
imports,
|
imports,
|
||||||
@ -100,10 +100,8 @@ impl Engine {
|
|||||||
|
|
||||||
global.lib.push(lib.clone());
|
global.lib.push(lib.clone());
|
||||||
|
|
||||||
Some(mem::replace(&mut global.constants, constants.clone()))
|
mem::replace(&mut global.constants, constants.clone())
|
||||||
} else {
|
});
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
if self.is_debugger_registered() {
|
if self.is_debugger_registered() {
|
||||||
|
@ -85,7 +85,7 @@ pub struct FuncInfoMetadata {
|
|||||||
pub return_type: Identifier,
|
pub return_type: Identifier,
|
||||||
/// Comments.
|
/// Comments.
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
pub comments: Box<[Identifier]>,
|
pub comments: Box<[SmartString]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A type containing a single registered function.
|
/// A type containing a single registered function.
|
||||||
@ -292,6 +292,11 @@ impl<M: Into<Module>> AddAssign<M> for Module {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn new_hash_map<T>(size: usize) -> StraightHashMap<T> {
|
||||||
|
StraightHashMap::with_capacity_and_hasher(size, Default::default())
|
||||||
|
}
|
||||||
|
|
||||||
impl Module {
|
impl Module {
|
||||||
/// Create a new [`Module`].
|
/// Create a new [`Module`].
|
||||||
///
|
///
|
||||||
@ -361,13 +366,7 @@ impl Module {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn set_id(&mut self, id: impl Into<ImmutableString>) -> &mut Self {
|
pub fn set_id(&mut self, id: impl Into<ImmutableString>) -> &mut Self {
|
||||||
let id = id.into();
|
let id = id.into();
|
||||||
|
self.id = (!id.is_empty()).then(|| id);
|
||||||
if id.is_empty() {
|
|
||||||
self.id = None;
|
|
||||||
} else {
|
|
||||||
self.id = Some(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -691,6 +690,16 @@ impl Module {
|
|||||||
|
|
||||||
if self.is_indexed() {
|
if self.is_indexed() {
|
||||||
let hash_var = crate::calc_var_hash(Some(""), &ident);
|
let hash_var = crate::calc_var_hash(Some(""), &ident);
|
||||||
|
|
||||||
|
// Catch hash collisions in testing environment only.
|
||||||
|
#[cfg(feature = "testing-environ")]
|
||||||
|
if let Some(_) = self.all_variables.as_ref().and_then(|f| f.get(&hash_var)) {
|
||||||
|
panic!(
|
||||||
|
"Hash {} already exists when registering variable {}",
|
||||||
|
hash_var, ident
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
self.all_variables
|
self.all_variables
|
||||||
.get_or_insert_with(Default::default)
|
.get_or_insert_with(Default::default)
|
||||||
.insert(hash_var, value.clone());
|
.insert(hash_var, value.clone());
|
||||||
@ -721,12 +730,21 @@ impl Module {
|
|||||||
// None + function name + number of arguments.
|
// None + function name + number of arguments.
|
||||||
let num_params = fn_def.params.len();
|
let num_params = fn_def.params.len();
|
||||||
let hash_script = crate::calc_fn_hash(None, &fn_def.name, num_params);
|
let hash_script = crate::calc_fn_hash(None, &fn_def.name, num_params);
|
||||||
|
|
||||||
|
// Catch hash collisions in testing environment only.
|
||||||
|
#[cfg(feature = "testing-environ")]
|
||||||
|
if let Some(f) = self.functions.as_ref().and_then(|f| f.get(&hash_script)) {
|
||||||
|
panic!(
|
||||||
|
"Hash {} already exists when registering function {:#?}:\n{:#?}",
|
||||||
|
hash_script, fn_def, f
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
let params_info = fn_def.params.iter().map(Into::into).collect();
|
let params_info = fn_def.params.iter().map(Into::into).collect();
|
||||||
|
|
||||||
self.functions
|
self.functions
|
||||||
.get_or_insert_with(|| {
|
.get_or_insert_with(|| new_hash_map(FN_MAP_SIZE))
|
||||||
StraightHashMap::with_capacity_and_hasher(FN_MAP_SIZE, Default::default())
|
|
||||||
})
|
|
||||||
.insert(
|
.insert(
|
||||||
hash_script,
|
hash_script,
|
||||||
FuncInfo {
|
FuncInfo {
|
||||||
@ -1052,6 +1070,15 @@ impl Module {
|
|||||||
let hash_script = calc_fn_hash(None, name, param_types.len());
|
let hash_script = calc_fn_hash(None, name, param_types.len());
|
||||||
let hash_fn = calc_fn_hash_full(hash_script, param_types.iter().copied());
|
let hash_fn = calc_fn_hash_full(hash_script, param_types.iter().copied());
|
||||||
|
|
||||||
|
// Catch hash collisions in testing environment only.
|
||||||
|
#[cfg(feature = "testing-environ")]
|
||||||
|
if let Some(f) = self.functions.as_ref().and_then(|f| f.get(&hash_script)) {
|
||||||
|
panic!(
|
||||||
|
"Hash {} already exists when registering function {}:\n{:#?}",
|
||||||
|
hash_script, name, f
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if is_dynamic {
|
if is_dynamic {
|
||||||
self.dynamic_functions_filter
|
self.dynamic_functions_filter
|
||||||
.get_or_insert_with(Default::default)
|
.get_or_insert_with(Default::default)
|
||||||
@ -1059,9 +1086,7 @@ impl Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.functions
|
self.functions
|
||||||
.get_or_insert_with(|| {
|
.get_or_insert_with(|| new_hash_map(FN_MAP_SIZE))
|
||||||
StraightHashMap::with_capacity_and_hasher(FN_MAP_SIZE, Default::default())
|
|
||||||
})
|
|
||||||
.insert(
|
.insert(
|
||||||
hash_fn,
|
hash_fn,
|
||||||
FuncInfo {
|
FuncInfo {
|
||||||
@ -1781,9 +1806,9 @@ impl Module {
|
|||||||
let others_len = functions.len();
|
let others_len = functions.len();
|
||||||
|
|
||||||
for (&k, f) in functions.iter() {
|
for (&k, f) in functions.iter() {
|
||||||
let map = self.functions.get_or_insert_with(|| {
|
let map = self
|
||||||
StraightHashMap::with_capacity_and_hasher(others_len, Default::default())
|
.functions
|
||||||
});
|
.get_or_insert_with(|| new_hash_map(FN_MAP_SIZE));
|
||||||
map.reserve(others_len);
|
map.reserve(others_len);
|
||||||
map.entry(k).or_insert_with(|| f.clone());
|
map.entry(k).or_insert_with(|| f.clone());
|
||||||
}
|
}
|
||||||
@ -2083,9 +2108,10 @@ impl Module {
|
|||||||
ast: &crate::AST,
|
ast: &crate::AST,
|
||||||
engine: &crate::Engine,
|
engine: &crate::Engine,
|
||||||
) -> RhaiResultOf<Self> {
|
) -> RhaiResultOf<Self> {
|
||||||
|
let mut scope = scope;
|
||||||
let global = &mut crate::eval::GlobalRuntimeState::new(engine);
|
let global = &mut crate::eval::GlobalRuntimeState::new(engine);
|
||||||
|
|
||||||
Self::eval_ast_as_new_raw(engine, scope, global, ast)
|
Self::eval_ast_as_new_raw(engine, &mut scope, global, ast)
|
||||||
}
|
}
|
||||||
/// Create a new [`Module`] by evaluating an [`AST`][crate::AST].
|
/// Create a new [`Module`] by evaluating an [`AST`][crate::AST].
|
||||||
///
|
///
|
||||||
@ -2101,13 +2127,12 @@ impl Module {
|
|||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
pub fn eval_ast_as_new_raw(
|
pub fn eval_ast_as_new_raw(
|
||||||
engine: &crate::Engine,
|
engine: &crate::Engine,
|
||||||
scope: crate::Scope,
|
scope: &mut crate::Scope,
|
||||||
global: &mut crate::eval::GlobalRuntimeState,
|
global: &mut crate::eval::GlobalRuntimeState,
|
||||||
ast: &crate::AST,
|
ast: &crate::AST,
|
||||||
) -> RhaiResultOf<Self> {
|
) -> RhaiResultOf<Self> {
|
||||||
let mut scope = scope;
|
|
||||||
|
|
||||||
// Save global state
|
// Save global state
|
||||||
|
let orig_scope_len = scope.len();
|
||||||
let orig_imports_len = global.num_imports();
|
let orig_imports_len = global.num_imports();
|
||||||
let orig_source = global.source.clone();
|
let orig_source = global.source.clone();
|
||||||
|
|
||||||
@ -2120,7 +2145,7 @@ impl Module {
|
|||||||
// Run the script
|
// Run the script
|
||||||
let caches = &mut crate::eval::Caches::new();
|
let caches = &mut crate::eval::Caches::new();
|
||||||
|
|
||||||
let result = engine.eval_ast_with_scope_raw(global, caches, &mut scope, ast);
|
let result = engine.eval_ast_with_scope_raw(global, caches, scope, ast);
|
||||||
|
|
||||||
// Create new module
|
// Create new module
|
||||||
let mut module = Module::new();
|
let mut module = Module::new();
|
||||||
@ -2162,7 +2187,9 @@ impl Module {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Variables with an alias left in the scope become module variables
|
// Variables with an alias left in the scope become module variables
|
||||||
for (_name, mut value, mut aliases) in scope {
|
while scope.len() > orig_scope_len {
|
||||||
|
let (_name, mut value, mut aliases) = scope.pop_entry().expect("not empty");
|
||||||
|
|
||||||
value.deep_scan(|v| {
|
value.deep_scan(|v| {
|
||||||
if let Some(fn_ptr) = v.downcast_mut::<crate::FnPtr>() {
|
if let Some(fn_ptr) = v.downcast_mut::<crate::FnPtr>() {
|
||||||
fn_ptr.set_encapsulated_environ(Some(environ.clone()));
|
fn_ptr.set_encapsulated_environ(Some(environ.clone()));
|
||||||
@ -2257,6 +2284,16 @@ impl Module {
|
|||||||
if let Some(ref v) = module.variables {
|
if let Some(ref v) = module.variables {
|
||||||
for (var_name, value) in v.iter() {
|
for (var_name, value) in v.iter() {
|
||||||
let hash_var = crate::calc_var_hash(path.iter().copied(), var_name);
|
let hash_var = crate::calc_var_hash(path.iter().copied(), var_name);
|
||||||
|
|
||||||
|
// Catch hash collisions in testing environment only.
|
||||||
|
#[cfg(feature = "testing-environ")]
|
||||||
|
if let Some(_) = variables.get(&hash_var) {
|
||||||
|
panic!(
|
||||||
|
"Hash {} already exists when indexing variable {}",
|
||||||
|
hash_var, var_name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
variables.insert(hash_var, value.clone());
|
variables.insert(hash_var, value.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2272,6 +2309,15 @@ impl Module {
|
|||||||
for (&hash, f) in module.functions.iter().flatten() {
|
for (&hash, f) in module.functions.iter().flatten() {
|
||||||
match f.metadata.namespace {
|
match f.metadata.namespace {
|
||||||
FnNamespace::Global => {
|
FnNamespace::Global => {
|
||||||
|
// Catch hash collisions in testing environment only.
|
||||||
|
#[cfg(feature = "testing-environ")]
|
||||||
|
if let Some(fx) = functions.get(&hash) {
|
||||||
|
panic!(
|
||||||
|
"Hash {} already exists when indexing function {:#?}:\n{:#?}",
|
||||||
|
hash, f.func, fx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Flatten all functions with global namespace
|
// Flatten all functions with global namespace
|
||||||
functions.insert(hash, f.func.clone());
|
functions.insert(hash, f.func.clone());
|
||||||
contains_indexed_global_functions = true;
|
contains_indexed_global_functions = true;
|
||||||
@ -2289,6 +2335,16 @@ impl Module {
|
|||||||
f.metadata.name.as_str(),
|
f.metadata.name.as_str(),
|
||||||
&f.metadata.param_types,
|
&f.metadata.param_types,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Catch hash collisions in testing environment only.
|
||||||
|
#[cfg(feature = "testing-environ")]
|
||||||
|
if let Some(fx) = functions.get(&hash_qualified_fn) {
|
||||||
|
panic!(
|
||||||
|
"Hash {} already exists when indexing function {:#?}:\n{:#?}",
|
||||||
|
hash_qualified_fn, f.func, fx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
functions.insert(hash_qualified_fn, f.func.clone());
|
functions.insert(hash_qualified_fn, f.func.clone());
|
||||||
} else if cfg!(not(feature = "no_function")) {
|
} else if cfg!(not(feature = "no_function")) {
|
||||||
let hash_qualified_script = crate::calc_fn_hash(
|
let hash_qualified_script = crate::calc_fn_hash(
|
||||||
@ -2296,6 +2352,16 @@ impl Module {
|
|||||||
&f.metadata.name,
|
&f.metadata.name,
|
||||||
f.metadata.num_params,
|
f.metadata.num_params,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Catch hash collisions in testing environment only.
|
||||||
|
#[cfg(feature = "testing-environ")]
|
||||||
|
if let Some(fx) = functions.get(&hash_qualified_script) {
|
||||||
|
panic!(
|
||||||
|
"Hash {} already exists when indexing function {:#?}:\n{:#?}",
|
||||||
|
hash_qualified_script, f.func, fx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
functions.insert(hash_qualified_script, f.func.clone());
|
functions.insert(hash_qualified_script, f.func.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2305,14 +2371,9 @@ impl Module {
|
|||||||
|
|
||||||
if !self.is_indexed() {
|
if !self.is_indexed() {
|
||||||
let mut path = Vec::with_capacity(4);
|
let mut path = Vec::with_capacity(4);
|
||||||
let mut variables = StraightHashMap::with_capacity_and_hasher(
|
let mut variables = new_hash_map(self.variables.as_deref().map_or(0, BTreeMap::len));
|
||||||
self.variables.as_deref().map_or(0, BTreeMap::len),
|
let mut functions =
|
||||||
Default::default(),
|
new_hash_map(self.functions.as_ref().map_or(0, StraightHashMap::len));
|
||||||
);
|
|
||||||
let mut functions = StraightHashMap::with_capacity_and_hasher(
|
|
||||||
self.functions.as_ref().map_or(0, StraightHashMap::len),
|
|
||||||
Default::default(),
|
|
||||||
);
|
|
||||||
let mut type_iterators = BTreeMap::new();
|
let mut type_iterators = BTreeMap::new();
|
||||||
|
|
||||||
path.push("");
|
path.push("");
|
||||||
@ -2328,21 +2389,9 @@ impl Module {
|
|||||||
self.flags
|
self.flags
|
||||||
.set(ModuleFlags::INDEXED_GLOBAL_FUNCTIONS, has_global_functions);
|
.set(ModuleFlags::INDEXED_GLOBAL_FUNCTIONS, has_global_functions);
|
||||||
|
|
||||||
self.all_variables = if variables.is_empty() {
|
self.all_variables = (!variables.is_empty()).then(|| variables.into());
|
||||||
None
|
self.all_functions = (!functions.is_empty()).then(|| functions.into());
|
||||||
} else {
|
self.all_type_iterators = (!type_iterators.is_empty()).then(|| type_iterators.into());
|
||||||
Some(variables.into())
|
|
||||||
};
|
|
||||||
self.all_functions = if functions.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(functions.into())
|
|
||||||
};
|
|
||||||
self.all_type_iterators = if type_iterators.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(type_iterators.into())
|
|
||||||
};
|
|
||||||
|
|
||||||
self.flags |= ModuleFlags::INDEXED;
|
self.flags |= ModuleFlags::INDEXED;
|
||||||
}
|
}
|
||||||
|
@ -239,14 +239,7 @@ impl FileModuleResolver {
|
|||||||
if !self.cache_enabled {
|
if !self.cache_enabled {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
locked_read(&self.cache).contains_key(path.as_ref())
|
||||||
let cache = locked_read(&self.cache);
|
|
||||||
|
|
||||||
if cache.is_empty() {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
cache.contains_key(path.as_ref())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/// Empty the internal cache.
|
/// Empty the internal cache.
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -290,15 +283,15 @@ impl FileModuleResolver {
|
|||||||
fn impl_resolve(
|
fn impl_resolve(
|
||||||
&self,
|
&self,
|
||||||
engine: &Engine,
|
engine: &Engine,
|
||||||
global: Option<&mut GlobalRuntimeState>,
|
global: &mut GlobalRuntimeState,
|
||||||
|
scope: &mut Scope,
|
||||||
source: Option<&str>,
|
source: Option<&str>,
|
||||||
path: &str,
|
path: &str,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
) -> Result<SharedModule, Box<crate::EvalAltResult>> {
|
) -> Result<SharedModule, Box<crate::EvalAltResult>> {
|
||||||
// Load relative paths from source if there is no base path specified
|
// Load relative paths from source if there is no base path specified
|
||||||
let source_path = global
|
let source_path = global
|
||||||
.as_ref()
|
.source()
|
||||||
.and_then(|g| g.source())
|
|
||||||
.or(source)
|
.or(source)
|
||||||
.and_then(|p| Path::new(p).parent());
|
.and_then(|p| Path::new(p).parent());
|
||||||
|
|
||||||
@ -321,12 +314,7 @@ impl FileModuleResolver {
|
|||||||
|
|
||||||
ast.set_source(path);
|
ast.set_source(path);
|
||||||
|
|
||||||
let scope = Scope::new();
|
let m: Shared<_> = Module::eval_ast_as_new_raw(engine, scope, global, &ast)
|
||||||
|
|
||||||
let m: Shared<_> = match global {
|
|
||||||
Some(global) => Module::eval_ast_as_new_raw(engine, scope, global, &ast),
|
|
||||||
None => Module::eval_ast_as_new(scope, &ast, engine),
|
|
||||||
}
|
|
||||||
.map_err(|err| Box::new(ERR::ErrorInModule(path.to_string(), err, pos)))?
|
.map_err(|err| Box::new(ERR::ErrorInModule(path.to_string(), err, pos)))?
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
@ -343,10 +331,11 @@ impl ModuleResolver for FileModuleResolver {
|
|||||||
&self,
|
&self,
|
||||||
engine: &Engine,
|
engine: &Engine,
|
||||||
global: &mut GlobalRuntimeState,
|
global: &mut GlobalRuntimeState,
|
||||||
|
scope: &mut Scope,
|
||||||
path: &str,
|
path: &str,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
) -> RhaiResultOf<SharedModule> {
|
) -> RhaiResultOf<SharedModule> {
|
||||||
self.impl_resolve(engine, Some(global), None, path, pos)
|
self.impl_resolve(engine, global, scope, None, path, pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -357,7 +346,9 @@ impl ModuleResolver for FileModuleResolver {
|
|||||||
path: &str,
|
path: &str,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
) -> RhaiResultOf<SharedModule> {
|
) -> RhaiResultOf<SharedModule> {
|
||||||
self.impl_resolve(engine, None, source, path, pos)
|
let global = &mut GlobalRuntimeState::new(engine);
|
||||||
|
let scope = &mut Scope::new();
|
||||||
|
self.impl_resolve(engine, global, scope, source, path, pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve an `AST` based on a path string.
|
/// Resolve an `AST` based on a path string.
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::eval::GlobalRuntimeState;
|
use crate::eval::GlobalRuntimeState;
|
||||||
use crate::func::SendSync;
|
use crate::func::SendSync;
|
||||||
use crate::{Engine, Position, RhaiResultOf, SharedModule, AST};
|
use crate::{Engine, Position, RhaiResultOf, Scope, SharedModule, AST};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
|
|
||||||
@ -27,15 +27,17 @@ pub trait ModuleResolver: SendSync {
|
|||||||
pos: Position,
|
pos: Position,
|
||||||
) -> RhaiResultOf<SharedModule>;
|
) -> RhaiResultOf<SharedModule>;
|
||||||
|
|
||||||
/// Resolve a module based on a path string, given a [`GlobalRuntimeState`].
|
/// Resolve a module based on a path string, given a [`GlobalRuntimeState`] and the current [`Scope`].
|
||||||
///
|
///
|
||||||
/// # WARNING - Low Level API
|
/// # WARNING - Low Level API
|
||||||
///
|
///
|
||||||
/// This function is very low level.
|
/// This function is very low level.
|
||||||
|
#[allow(unused_variables)]
|
||||||
fn resolve_raw(
|
fn resolve_raw(
|
||||||
&self,
|
&self,
|
||||||
engine: &Engine,
|
engine: &Engine,
|
||||||
global: &mut GlobalRuntimeState,
|
global: &mut GlobalRuntimeState,
|
||||||
|
scope: &mut Scope,
|
||||||
path: &str,
|
path: &str,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
) -> RhaiResultOf<SharedModule> {
|
) -> RhaiResultOf<SharedModule> {
|
||||||
|
@ -73,12 +73,8 @@ impl StaticModuleResolver {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn contains_path(&self, path: &str) -> bool {
|
pub fn contains_path(&self, path: &str) -> bool {
|
||||||
if self.0.is_empty() {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
self.0.contains_key(path)
|
self.0.contains_key(path)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
/// Get an iterator of all the [modules][Module].
|
/// Get an iterator of all the [modules][Module].
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn iter(&self) -> impl Iterator<Item = (&str, &SharedModule)> {
|
pub fn iter(&self) -> impl Iterator<Item = (&str, &SharedModule)> {
|
||||||
@ -123,9 +119,7 @@ impl StaticModuleResolver {
|
|||||||
/// Existing modules of the same path name are overwritten.
|
/// Existing modules of the same path name are overwritten.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn merge(&mut self, other: Self) -> &mut Self {
|
pub fn merge(&mut self, other: Self) -> &mut Self {
|
||||||
if !other.is_empty() {
|
|
||||||
self.0.extend(other.0.into_iter());
|
self.0.extend(other.0.into_iter());
|
||||||
}
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,9 @@ use crate::ast::{
|
|||||||
ASTFlags, Expr, FlowControl, OpAssignment, Stmt, StmtBlock, StmtBlockContainer,
|
ASTFlags, Expr, FlowControl, OpAssignment, Stmt, StmtBlock, StmtBlockContainer,
|
||||||
SwitchCasesCollection,
|
SwitchCasesCollection,
|
||||||
};
|
};
|
||||||
use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF};
|
use crate::engine::{
|
||||||
|
KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF, OP_NOT,
|
||||||
|
};
|
||||||
use crate::eval::{Caches, GlobalRuntimeState};
|
use crate::eval::{Caches, GlobalRuntimeState};
|
||||||
use crate::func::builtin::get_builtin_binary_op_fn;
|
use crate::func::builtin::get_builtin_binary_op_fn;
|
||||||
use crate::func::hashing::get_hasher;
|
use crate::func::hashing::get_hasher;
|
||||||
@ -25,9 +27,6 @@ use std::{
|
|||||||
mem,
|
mem,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Standard not operator.
|
|
||||||
const OP_NOT: &str = Token::Bang.literal_syntax();
|
|
||||||
|
|
||||||
/// Level of optimization performed.
|
/// Level of optimization performed.
|
||||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
|
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
@ -564,16 +563,14 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Then check ranges
|
// Then check ranges
|
||||||
if value.is_int() && !ranges.is_empty() {
|
if !ranges.is_empty() {
|
||||||
let value = value.as_int().unwrap();
|
|
||||||
|
|
||||||
// Only one range or all ranges without conditions
|
// Only one range or all ranges without conditions
|
||||||
if ranges.len() == 1
|
if ranges.len() == 1
|
||||||
|| ranges
|
|| ranges
|
||||||
.iter()
|
.iter()
|
||||||
.all(|r| expressions[r.index()].is_always_true())
|
.all(|r| expressions[r.index()].is_always_true())
|
||||||
{
|
{
|
||||||
if let Some(r) = ranges.iter().find(|r| r.contains(value)) {
|
if let Some(r) = ranges.iter().find(|r| r.contains(&value)) {
|
||||||
let range_block = &mut expressions[r.index()];
|
let range_block = &mut expressions[r.index()];
|
||||||
|
|
||||||
if range_block.is_always_true() {
|
if range_block.is_always_true() {
|
||||||
@ -620,7 +617,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
|
|
||||||
let old_ranges_len = ranges.len();
|
let old_ranges_len = ranges.len();
|
||||||
|
|
||||||
ranges.retain(|r| r.contains(value));
|
ranges.retain(|r| r.contains(&value));
|
||||||
|
|
||||||
if ranges.len() != old_ranges_len {
|
if ranges.len() != old_ranges_len {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
@ -1140,13 +1137,9 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
|
|||||||
_ if x.args.len() == 2 && x.op_token.is_some() && (state.engine.fast_operators() || !state.engine.has_native_fn_override(x.hashes.native(), &arg_types)) => {
|
_ if x.args.len() == 2 && x.op_token.is_some() && (state.engine.fast_operators() || !state.engine.has_native_fn_override(x.hashes.native(), &arg_types)) => {
|
||||||
if let Some(result) = get_builtin_binary_op_fn(x.op_token.as_ref().unwrap(), &arg_values[0], &arg_values[1])
|
if let Some(result) = get_builtin_binary_op_fn(x.op_token.as_ref().unwrap(), &arg_values[0], &arg_values[1])
|
||||||
.and_then(|(f, ctx)| {
|
.and_then(|(f, ctx)| {
|
||||||
let context = if ctx {
|
let context = ctx.then(|| (state.engine, x.name.as_str(), None, &state.global, *pos).into());
|
||||||
Some((state.engine, x.name.as_str(), None, &state.global, *pos).into())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let (first, second) = arg_values.split_first_mut().unwrap();
|
let (first, second) = arg_values.split_first_mut().unwrap();
|
||||||
(f)(context, &mut [ first, &mut second[0] ]).ok()
|
f(context, &mut [ first, &mut second[0] ]).ok()
|
||||||
}) {
|
}) {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
*expr = Expr::from_dynamic(result, *pos);
|
*expr = Expr::from_dynamic(result, *pos);
|
||||||
|
@ -5,6 +5,7 @@ use crate::engine::OP_EQUALS;
|
|||||||
use crate::eval::{calc_index, calc_offset_len};
|
use crate::eval::{calc_index, calc_offset_len};
|
||||||
use crate::module::ModuleFlags;
|
use crate::module::ModuleFlags;
|
||||||
use crate::plugin::*;
|
use crate::plugin::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
def_package, Array, Dynamic, ExclusiveRange, FnPtr, InclusiveRange, NativeCallContext,
|
def_package, Array, Dynamic, ExclusiveRange, FnPtr, InclusiveRange, NativeCallContext,
|
||||||
Position, RhaiResultOf, StaticVec, ERR, INT, MAX_USIZE_INT,
|
Position, RhaiResultOf, StaticVec, ERR, INT, MAX_USIZE_INT,
|
||||||
@ -237,7 +238,7 @@ pub mod array_functions {
|
|||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
if _ctx.engine().max_array_size() > 0 {
|
if _ctx.engine().max_array_size() > 0 {
|
||||||
let pad = len - array.len();
|
let pad = len - array.len();
|
||||||
let (a, m, s) = Dynamic::calc_array_sizes(array);
|
let (a, m, s) = crate::eval::calc_array_sizes(array);
|
||||||
let (ax, mx, sx) = item.calc_data_sizes(true);
|
let (ax, mx, sx) = item.calc_data_sizes(true);
|
||||||
|
|
||||||
_ctx.engine()
|
_ctx.engine()
|
||||||
|
@ -7,6 +7,7 @@ use crate::{Dynamic, RhaiResultOf, ERR, INT};
|
|||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
#[cfg(not(feature = "no_std"))]
|
||||||
use crate::FLOAT;
|
use crate::FLOAT;
|
||||||
|
|
||||||
def_package! {
|
def_package! {
|
||||||
@ -287,7 +288,8 @@ fn collect_fn_metadata(
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
{
|
{
|
||||||
use crate::{tokenizer::Token::DoubleColon, Shared, SmartString};
|
use crate::engine::NAMESPACE_SEPARATOR;
|
||||||
|
use crate::{Shared, SmartString};
|
||||||
|
|
||||||
// Recursively scan modules for script-defined functions.
|
// Recursively scan modules for script-defined functions.
|
||||||
fn scan_module(
|
fn scan_module(
|
||||||
@ -305,7 +307,7 @@ fn collect_fn_metadata(
|
|||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
let mut ns = SmartString::new_const();
|
let mut ns = SmartString::new_const();
|
||||||
write!(&mut ns, "{namespace}{}{name}", DoubleColon.literal_syntax()).unwrap();
|
write!(&mut ns, "{namespace}{}{name}", NAMESPACE_SEPARATOR).unwrap();
|
||||||
scan_module(engine, list, &ns, m, filter);
|
scan_module(engine, list, &ns, m, filter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
109
src/parser.rs
109
src/parser.rs
@ -7,25 +7,24 @@ use crate::ast::{
|
|||||||
FnCallHashes, Ident, Namespace, OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock,
|
FnCallHashes, Ident, Namespace, OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock,
|
||||||
StmtBlockContainer, SwitchCasesCollection,
|
StmtBlockContainer, SwitchCasesCollection,
|
||||||
};
|
};
|
||||||
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
|
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS, OP_NOT};
|
||||||
use crate::eval::{Caches, GlobalRuntimeState};
|
use crate::eval::{Caches, GlobalRuntimeState};
|
||||||
use crate::func::{hashing::get_hasher, StraightHashMap};
|
use crate::func::{hashing::get_hasher, StraightHashMap};
|
||||||
use crate::tokenizer::{
|
use crate::tokenizer::{
|
||||||
is_keyword_function, is_valid_function_name, is_valid_identifier, Token, TokenStream,
|
is_reserved_keyword_or_symbol, is_valid_function_name, is_valid_identifier, Token, TokenStream,
|
||||||
TokenizerControl,
|
TokenizerControl,
|
||||||
};
|
};
|
||||||
use crate::types::dynamic::AccessMode;
|
use crate::types::dynamic::{AccessMode, Union};
|
||||||
use crate::types::StringsInterner;
|
use crate::types::StringsInterner;
|
||||||
use crate::{
|
use crate::{
|
||||||
calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ExclusiveRange, FnArgsVec,
|
calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ExclusiveRange, FnArgsVec,
|
||||||
Identifier, ImmutableString, InclusiveRange, LexError, OptimizationLevel, ParseError, Position,
|
Identifier, ImmutableString, InclusiveRange, LexError, OptimizationLevel, ParseError, Position,
|
||||||
Scope, Shared, SmartString, StaticVec, AST, INT, PERR,
|
Scope, Shared, SmartString, StaticVec, AST, PERR,
|
||||||
};
|
};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
use std::{
|
use std::{
|
||||||
collections::BTreeMap,
|
|
||||||
convert::TryFrom,
|
convert::TryFrom,
|
||||||
fmt,
|
fmt,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
@ -42,9 +41,6 @@ const SCOPE_SEARCH_BARRIER_MARKER: &str = "$ BARRIER $";
|
|||||||
/// The message: `TokenStream` never ends
|
/// The message: `TokenStream` never ends
|
||||||
const NEVER_ENDS: &str = "`Token`";
|
const NEVER_ENDS: &str = "`Token`";
|
||||||
|
|
||||||
/// Unroll `switch` ranges no larger than this.
|
|
||||||
const SMALL_SWITCH_RANGE: INT = 16;
|
|
||||||
|
|
||||||
/// _(internals)_ A type that encapsulates the current state of the parser.
|
/// _(internals)_ A type that encapsulates the current state of the parser.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
pub struct ParseState<'e, 's> {
|
pub struct ParseState<'e, 's> {
|
||||||
@ -218,11 +214,7 @@ impl<'e, 's> ParseState<'e, 's> {
|
|||||||
self.allow_capture = true;
|
self.allow_capture = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let index = if hit_barrier {
|
let index = (!hit_barrier).then(|| NonZeroUsize::new(index)).flatten();
|
||||||
None
|
|
||||||
} else {
|
|
||||||
NonZeroUsize::new(index)
|
|
||||||
};
|
|
||||||
|
|
||||||
(index, is_func_name)
|
(index, is_func_name)
|
||||||
}
|
}
|
||||||
@ -986,7 +978,7 @@ impl Engine {
|
|||||||
settings.pos = eat_token(input, Token::MapStart);
|
settings.pos = eat_token(input, Token::MapStart);
|
||||||
|
|
||||||
let mut map = StaticVec::<(Ident, Expr)>::new();
|
let mut map = StaticVec::<(Ident, Expr)>::new();
|
||||||
let mut template = BTreeMap::<Identifier, crate::Dynamic>::new();
|
let mut template = std::collections::BTreeMap::<Identifier, crate::Dynamic>::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
const MISSING_RBRACE: &str = "to end this object map literal";
|
const MISSING_RBRACE: &str = "to end this object map literal";
|
||||||
@ -1121,7 +1113,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut expressions = StaticVec::<ConditionalExpr>::new();
|
let mut expressions = StaticVec::<ConditionalExpr>::new();
|
||||||
let mut cases = BTreeMap::<u64, CaseBlocksList>::new();
|
let mut cases = StraightHashMap::<CaseBlocksList>::default();
|
||||||
let mut ranges = StaticVec::<RangeCase>::new();
|
let mut ranges = StaticVec::<RangeCase>::new();
|
||||||
let mut def_case = None;
|
let mut def_case = None;
|
||||||
let mut def_case_pos = Position::NONE;
|
let mut def_case_pos = Position::NONE;
|
||||||
@ -1216,7 +1208,6 @@ impl Engine {
|
|||||||
let stmt_block: StmtBlock = stmt.into();
|
let stmt_block: StmtBlock = stmt.into();
|
||||||
(Expr::Stmt(stmt_block.into()), need_comma)
|
(Expr::Stmt(stmt_block.into()), need_comma)
|
||||||
};
|
};
|
||||||
let has_condition = !matches!(condition, Expr::BoolConstant(true, ..));
|
|
||||||
|
|
||||||
expressions.push((condition, action_expr).into());
|
expressions.push((condition, action_expr).into());
|
||||||
let index = expressions.len() - 1;
|
let index = expressions.len() - 1;
|
||||||
@ -1240,29 +1231,28 @@ impl Engine {
|
|||||||
|
|
||||||
if let Some(mut r) = range_value {
|
if let Some(mut r) = range_value {
|
||||||
if !r.is_empty() {
|
if !r.is_empty() {
|
||||||
// Do not unroll ranges if there are previous non-unrolled ranges
|
|
||||||
if !has_condition && ranges.is_empty() && r.len() <= SMALL_SWITCH_RANGE
|
|
||||||
{
|
|
||||||
// Unroll small range
|
|
||||||
r.into_iter().for_each(|n| {
|
|
||||||
let hasher = &mut get_hasher();
|
|
||||||
Dynamic::from_int(n).hash(hasher);
|
|
||||||
cases
|
|
||||||
.entry(hasher.finish())
|
|
||||||
.and_modify(|cases| cases.push(index))
|
|
||||||
.or_insert_with(|| [index].into());
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Other range
|
// Other range
|
||||||
r.set_index(index);
|
r.set_index(index);
|
||||||
ranges.push(r);
|
ranges.push(r);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if value.is_int() && !ranges.is_empty() {
|
if !ranges.is_empty() {
|
||||||
return Err(PERR::WrongSwitchIntegerCase.into_err(expr.start_position()));
|
let forbidden = match value {
|
||||||
|
Dynamic(Union::Int(..)) => true,
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
Dynamic(Union::Float(..)) => true,
|
||||||
|
#[cfg(feature = "decimal")]
|
||||||
|
Dynamic(Union::Decimal(..)) => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if forbidden {
|
||||||
|
return Err(
|
||||||
|
PERR::WrongSwitchIntegerCase.into_err(expr.start_position())
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let hasher = &mut get_hasher();
|
let hasher = &mut get_hasher();
|
||||||
@ -1299,6 +1289,10 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expressions.shrink_to_fit();
|
||||||
|
cases.shrink_to_fit();
|
||||||
|
ranges.shrink_to_fit();
|
||||||
|
|
||||||
let cases = SwitchCasesCollection {
|
let cases = SwitchCasesCollection {
|
||||||
expressions,
|
expressions,
|
||||||
cases,
|
cases,
|
||||||
@ -1398,20 +1392,26 @@ impl Engine {
|
|||||||
.into(),
|
.into(),
|
||||||
)),
|
)),
|
||||||
// Loops are allowed to act as expressions
|
// Loops are allowed to act as expressions
|
||||||
Token::While | Token::Loop if settings.has_option(LangOptions::LOOP_EXPR) => {
|
Token::While | Token::Loop
|
||||||
|
if self.allow_looping() && settings.has_option(LangOptions::LOOP_EXPR) =>
|
||||||
|
{
|
||||||
Expr::Stmt(Box::new(
|
Expr::Stmt(Box::new(
|
||||||
self.parse_while_loop(input, state, lib, settings.level_up()?)?
|
self.parse_while_loop(input, state, lib, settings.level_up()?)?
|
||||||
.into(),
|
.into(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
Token::Do if settings.has_option(LangOptions::LOOP_EXPR) => Expr::Stmt(Box::new(
|
Token::Do if self.allow_looping() && settings.has_option(LangOptions::LOOP_EXPR) => {
|
||||||
|
Expr::Stmt(Box::new(
|
||||||
self.parse_do(input, state, lib, settings.level_up()?)?
|
self.parse_do(input, state, lib, settings.level_up()?)?
|
||||||
.into(),
|
.into(),
|
||||||
)),
|
))
|
||||||
Token::For if settings.has_option(LangOptions::LOOP_EXPR) => Expr::Stmt(Box::new(
|
}
|
||||||
|
Token::For if self.allow_looping() && settings.has_option(LangOptions::LOOP_EXPR) => {
|
||||||
|
Expr::Stmt(Box::new(
|
||||||
self.parse_for(input, state, lib, settings.level_up()?)?
|
self.parse_for(input, state, lib, settings.level_up()?)?
|
||||||
.into(),
|
.into(),
|
||||||
)),
|
))
|
||||||
|
}
|
||||||
// Switch statement is allowed to act as expressions
|
// Switch statement is allowed to act as expressions
|
||||||
Token::Switch if settings.has_option(LangOptions::SWITCH_EXPR) => Expr::Stmt(Box::new(
|
Token::Switch if settings.has_option(LangOptions::SWITCH_EXPR) => Expr::Stmt(Box::new(
|
||||||
self.parse_switch(input, state, lib, settings.level_up()?)?
|
self.parse_switch(input, state, lib, settings.level_up()?)?
|
||||||
@ -1665,7 +1665,9 @@ impl Engine {
|
|||||||
|
|
||||||
match input.peek().expect(NEVER_ENDS).0 {
|
match input.peek().expect(NEVER_ENDS).0 {
|
||||||
// Function call is allowed to have reserved keyword
|
// Function call is allowed to have reserved keyword
|
||||||
Token::LeftParen | Token::Bang | Token::Unit if is_keyword_function(&s).0 => {
|
Token::LeftParen | Token::Bang | Token::Unit
|
||||||
|
if is_reserved_keyword_or_symbol(&s).1 =>
|
||||||
|
{
|
||||||
Expr::Variable(
|
Expr::Variable(
|
||||||
(None, ns, 0, state.get_interned_string(*s)).into(),
|
(None, ns, 0, state.get_interned_string(*s)).into(),
|
||||||
None,
|
None,
|
||||||
@ -1722,7 +1724,7 @@ impl Engine {
|
|||||||
lib: &mut FnLib,
|
lib: &mut FnLib,
|
||||||
settings: ParseSettings,
|
settings: ParseSettings,
|
||||||
mut lhs: Expr,
|
mut lhs: Expr,
|
||||||
options: ChainingFlags,
|
_options: ChainingFlags,
|
||||||
) -> ParseResult<Expr> {
|
) -> ParseResult<Expr> {
|
||||||
let mut settings = settings;
|
let mut settings = settings;
|
||||||
|
|
||||||
@ -1783,7 +1785,7 @@ impl Engine {
|
|||||||
// Disallowed module separator
|
// Disallowed module separator
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
(_, token @ Token::DoubleColon)
|
(_, token @ Token::DoubleColon)
|
||||||
if options.contains(ChainingFlags::DISALLOW_NAMESPACES) =>
|
if _options.contains(ChainingFlags::DISALLOW_NAMESPACES) =>
|
||||||
{
|
{
|
||||||
return Err(LexError::ImproperSymbol(
|
return Err(LexError::ImproperSymbol(
|
||||||
token.literal_syntax().into(),
|
token.literal_syntax().into(),
|
||||||
@ -1824,7 +1826,7 @@ impl Engine {
|
|||||||
// Prevents capturing of the object properties as vars: xxx.<var>
|
// Prevents capturing of the object properties as vars: xxx.<var>
|
||||||
state.allow_capture = false;
|
state.allow_capture = false;
|
||||||
}
|
}
|
||||||
(Token::Reserved(s), ..) if is_keyword_function(s).1 => (),
|
(Token::Reserved(s), ..) if is_reserved_keyword_or_symbol(s).2 => (),
|
||||||
(Token::Reserved(s), pos) => {
|
(Token::Reserved(s), pos) => {
|
||||||
return Err(PERR::Reserved(s.to_string()).into_err(*pos))
|
return Err(PERR::Reserved(s.to_string()).into_err(*pos))
|
||||||
}
|
}
|
||||||
@ -2352,12 +2354,7 @@ impl Engine {
|
|||||||
|
|
||||||
let op = op_token.to_string();
|
let op = op_token.to_string();
|
||||||
let hash = calc_fn_hash(None, &op, 2);
|
let hash = calc_fn_hash(None, &op, 2);
|
||||||
let is_valid_script_function = is_valid_function_name(&op);
|
let native_only = !is_valid_function_name(&op);
|
||||||
let operator_token = if is_valid_script_function {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(op_token.clone())
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut args = FnArgsVec::new_const();
|
let mut args = FnArgsVec::new_const();
|
||||||
args.push(root);
|
args.push(root);
|
||||||
@ -2369,7 +2366,7 @@ impl Engine {
|
|||||||
name: state.get_interned_string(&op),
|
name: state.get_interned_string(&op),
|
||||||
hashes: FnCallHashes::from_native_only(hash),
|
hashes: FnCallHashes::from_native_only(hash),
|
||||||
args,
|
args,
|
||||||
op_token: operator_token,
|
op_token: native_only.then(|| op_token.clone()),
|
||||||
capture_parent_scope: false,
|
capture_parent_scope: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2418,14 +2415,13 @@ impl Engine {
|
|||||||
fn_call
|
fn_call
|
||||||
} else {
|
} else {
|
||||||
// Put a `!` call in front
|
// Put a `!` call in front
|
||||||
let op = Token::Bang.literal_syntax();
|
|
||||||
let mut args = FnArgsVec::new_const();
|
let mut args = FnArgsVec::new_const();
|
||||||
args.push(fn_call);
|
args.push(fn_call);
|
||||||
|
|
||||||
let not_base = FnCallExpr {
|
let not_base = FnCallExpr {
|
||||||
namespace: Namespace::NONE,
|
namespace: Namespace::NONE,
|
||||||
name: state.get_interned_string(op),
|
name: state.get_interned_string(OP_NOT),
|
||||||
hashes: FnCallHashes::from_native_only(calc_fn_hash(None, op, 1)),
|
hashes: FnCallHashes::from_native_only(calc_fn_hash(None, OP_NOT, 1)),
|
||||||
args,
|
args,
|
||||||
op_token: Some(Token::Bang),
|
op_token: Some(Token::Bang),
|
||||||
capture_parent_scope: false,
|
capture_parent_scope: false,
|
||||||
@ -2442,10 +2438,10 @@ impl Engine {
|
|||||||
.and_then(|m| m.get(s.as_str()))
|
.and_then(|m| m.get(s.as_str()))
|
||||||
.map_or(false, Option::is_some) =>
|
.map_or(false, Option::is_some) =>
|
||||||
{
|
{
|
||||||
op_base.hashes = if is_valid_script_function {
|
op_base.hashes = if native_only {
|
||||||
FnCallHashes::from_hash(calc_fn_hash(None, &s, 2))
|
|
||||||
} else {
|
|
||||||
FnCallHashes::from_native_only(calc_fn_hash(None, &s, 2))
|
FnCallHashes::from_native_only(calc_fn_hash(None, &s, 2))
|
||||||
|
} else {
|
||||||
|
FnCallHashes::from_hash(calc_fn_hash(None, &s, 2))
|
||||||
};
|
};
|
||||||
op_base.into_fn_call_expr(pos)
|
op_base.into_fn_call_expr(pos)
|
||||||
}
|
}
|
||||||
@ -3081,8 +3077,7 @@ impl Engine {
|
|||||||
let (id, id_pos) = parse_var_name(input)?;
|
let (id, id_pos) = parse_var_name(input)?;
|
||||||
|
|
||||||
let (alias, alias_pos) = if match_token(input, Token::As).0 {
|
let (alias, alias_pos) = if match_token(input, Token::As).0 {
|
||||||
let (name, pos) = parse_var_name(input)?;
|
parse_var_name(input).map(|(name, pos)| (Some(name), pos))?
|
||||||
(Some(name), pos)
|
|
||||||
} else {
|
} else {
|
||||||
(None, Position::NONE)
|
(None, Position::NONE)
|
||||||
};
|
};
|
||||||
@ -3788,7 +3783,7 @@ impl Engine {
|
|||||||
(.., pos) => {
|
(.., pos) => {
|
||||||
return Err(PERR::MissingToken(
|
return Err(PERR::MissingToken(
|
||||||
Token::Pipe.into(),
|
Token::Pipe.into(),
|
||||||
"to close the parameters list of anonymous function".into(),
|
"to close the parameters list of anonymous function or closure".into(),
|
||||||
)
|
)
|
||||||
.into_err(pos))
|
.into_err(pos))
|
||||||
}
|
}
|
||||||
|
43
src/tests.rs
43
src/tests.rs
@ -13,6 +13,7 @@ fn check_struct_sizes() {
|
|||||||
feature = "only_i32",
|
feature = "only_i32",
|
||||||
any(feature = "no_float", feature = "f32_float")
|
any(feature = "no_float", feature = "f32_float")
|
||||||
));
|
));
|
||||||
|
const WORD_SIZE: usize = size_of::<usize>();
|
||||||
|
|
||||||
assert_eq!(size_of::<Dynamic>(), if PACKED { 8 } else { 16 });
|
assert_eq!(size_of::<Dynamic>(), if PACKED { 8 } else { 16 });
|
||||||
assert_eq!(size_of::<Option<Dynamic>>(), if PACKED { 8 } else { 16 });
|
assert_eq!(size_of::<Option<Dynamic>>(), if PACKED { 8 } else { 16 });
|
||||||
@ -20,10 +21,7 @@ fn check_struct_sizes() {
|
|||||||
size_of::<Position>(),
|
size_of::<Position>(),
|
||||||
if cfg!(feature = "no_position") { 0 } else { 4 }
|
if cfg!(feature = "no_position") { 0 } else { 4 }
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(size_of::<tokenizer::Token>(), 2 * WORD_SIZE);
|
||||||
size_of::<tokenizer::Token>(),
|
|
||||||
if IS_32_BIT { 8 } else { 16 }
|
|
||||||
);
|
|
||||||
assert_eq!(size_of::<ast::Expr>(), if PACKED { 12 } else { 16 });
|
assert_eq!(size_of::<ast::Expr>(), if PACKED { 12 } else { 16 });
|
||||||
assert_eq!(size_of::<Option<ast::Expr>>(), if PACKED { 12 } else { 16 });
|
assert_eq!(size_of::<Option<ast::Expr>>(), if PACKED { 12 } else { 16 });
|
||||||
assert_eq!(size_of::<ast::Stmt>(), if IS_32_BIT { 12 } else { 16 });
|
assert_eq!(size_of::<ast::Stmt>(), if IS_32_BIT { 12 } else { 16 });
|
||||||
@ -34,40 +32,41 @@ fn check_struct_sizes() {
|
|||||||
|
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
{
|
{
|
||||||
assert_eq!(
|
assert_eq!(size_of::<CallableFunction>(), 3 * WORD_SIZE);
|
||||||
size_of::<CallableFunction>(),
|
assert_eq!(size_of::<module::FuncInfo>(), 4 * WORD_SIZE);
|
||||||
if IS_32_BIT { 12 } else { 24 }
|
}
|
||||||
);
|
|
||||||
assert_eq!(
|
// The following only on 64-bit platforms
|
||||||
size_of::<module::FuncInfo>(),
|
|
||||||
if IS_32_BIT { 16 } else { 32 }
|
if !cfg!(target_pointer_width = "64") {
|
||||||
);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_pointer_width = "64")]
|
|
||||||
{
|
|
||||||
assert_eq!(size_of::<Scope>(), 536);
|
assert_eq!(size_of::<Scope>(), 536);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
size_of::<FnPtr>(),
|
size_of::<FnPtr>(),
|
||||||
if cfg!(feature = "no_function") {
|
80 - if cfg!(feature = "no_function") {
|
||||||
72
|
WORD_SIZE
|
||||||
} else {
|
} else {
|
||||||
80
|
0
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
assert_eq!(size_of::<LexError>(), 56);
|
assert_eq!(size_of::<LexError>(), 56);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
size_of::<ParseError>(),
|
size_of::<ParseError>(),
|
||||||
if cfg!(feature = "no_position") { 8 } else { 16 }
|
16 - if cfg!(feature = "no_position") {
|
||||||
|
WORD_SIZE
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
);
|
);
|
||||||
assert_eq!(size_of::<EvalAltResult>(), 64);
|
assert_eq!(size_of::<EvalAltResult>(), 64);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
size_of::<NativeCallContext>(),
|
size_of::<NativeCallContext>(),
|
||||||
if cfg!(feature = "no_position") {
|
56 - if cfg!(feature = "no_position") {
|
||||||
48
|
WORD_SIZE
|
||||||
} else {
|
} else {
|
||||||
56
|
0
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
610
src/tokenizer.rs
610
src/tokenizer.rs
@ -1,9 +1,6 @@
|
|||||||
//! Main module defining the lexer and parser.
|
//! Main module defining the lexer and parser.
|
||||||
|
|
||||||
use crate::engine::{
|
use crate::engine::Precedence;
|
||||||
Precedence, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL,
|
|
||||||
KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
|
|
||||||
};
|
|
||||||
use crate::func::native::OnParseTokenCallback;
|
use crate::func::native::OnParseTokenCallback;
|
||||||
use crate::{Engine, Identifier, LexError, Position, SmartString, StaticVec, INT, UNSIGNED_INT};
|
use crate::{Engine, Identifier, LexError, Position, SmartString, StaticVec, INT, UNSIGNED_INT};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
@ -308,6 +305,348 @@ impl fmt::Display for Token {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Table-driven keyword recognizer generated by GNU gperf on the file `tools/keywords.txt`.
|
||||||
|
//
|
||||||
|
// When adding new keywords, make sure to update `tools/keywords.txt` and re-generate this.
|
||||||
|
|
||||||
|
const MIN_KEYWORD_LEN: usize = 1;
|
||||||
|
const MAX_KEYWORD_LEN: usize = 8;
|
||||||
|
const MIN_KEYWORD_HASH_VALUE: usize = 1;
|
||||||
|
const MAX_KEYWORD_HASH_VALUE: usize = 152;
|
||||||
|
|
||||||
|
static KEYWORD_ASSOC_VALUES: [u8; 257] = [
|
||||||
|
153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153,
|
||||||
|
153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 115, 153, 100, 153, 110,
|
||||||
|
105, 40, 80, 2, 20, 25, 125, 95, 15, 40, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 55,
|
||||||
|
35, 10, 5, 0, 30, 110, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153,
|
||||||
|
153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 120, 105, 100, 85, 90, 153, 125, 5,
|
||||||
|
0, 125, 35, 10, 100, 153, 20, 0, 153, 10, 0, 45, 55, 0, 153, 50, 55, 5, 0, 153, 0, 0, 35, 153,
|
||||||
|
45, 50, 30, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153,
|
||||||
|
153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153,
|
||||||
|
153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153,
|
||||||
|
153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153,
|
||||||
|
153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153,
|
||||||
|
153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153,
|
||||||
|
153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153,
|
||||||
|
153,
|
||||||
|
];
|
||||||
|
static KEYWORDS_LIST: [(&str, Token); 153] = [
|
||||||
|
("", Token::EOF),
|
||||||
|
(">", Token::GreaterThan),
|
||||||
|
(">=", Token::GreaterThanEqualsTo),
|
||||||
|
(")", Token::RightParen),
|
||||||
|
("", Token::EOF),
|
||||||
|
("const", Token::Const),
|
||||||
|
("=", Token::Equals),
|
||||||
|
("==", Token::EqualsTo),
|
||||||
|
("continue", Token::Continue),
|
||||||
|
("", Token::EOF),
|
||||||
|
("catch", Token::Catch),
|
||||||
|
("<", Token::LessThan),
|
||||||
|
("<=", Token::LessThanEqualsTo),
|
||||||
|
("for", Token::For),
|
||||||
|
("loop", Token::Loop),
|
||||||
|
("", Token::EOF),
|
||||||
|
(".", Token::Period),
|
||||||
|
("<<", Token::LeftShift),
|
||||||
|
("<<=", Token::LeftShiftAssign),
|
||||||
|
("", Token::EOF),
|
||||||
|
("false", Token::False),
|
||||||
|
("*", Token::Multiply),
|
||||||
|
("*=", Token::MultiplyAssign),
|
||||||
|
("let", Token::Let),
|
||||||
|
("", Token::EOF),
|
||||||
|
("while", Token::While),
|
||||||
|
("+", Token::Plus),
|
||||||
|
("+=", Token::PlusAssign),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("throw", Token::Throw),
|
||||||
|
("}", Token::RightBrace),
|
||||||
|
(">>", Token::RightShift),
|
||||||
|
(">>=", Token::RightShiftAssign),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
(";", Token::SemiColon),
|
||||||
|
("=>", Token::DoubleArrow),
|
||||||
|
("", Token::EOF),
|
||||||
|
("else", Token::Else),
|
||||||
|
("", Token::EOF),
|
||||||
|
("/", Token::Divide),
|
||||||
|
("/=", Token::DivideAssign),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("{", Token::LeftBrace),
|
||||||
|
("**", Token::PowerOf),
|
||||||
|
("**=", Token::PowerOfAssign),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("|", Token::Pipe),
|
||||||
|
("|=", Token::OrAssign),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
(":", Token::Colon),
|
||||||
|
("..", Token::ExclusiveRange),
|
||||||
|
("..=", Token::InclusiveRange),
|
||||||
|
("", Token::EOF),
|
||||||
|
("until", Token::Until),
|
||||||
|
("switch", Token::Switch),
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
("private", Token::Private),
|
||||||
|
#[cfg(feature = "no_function")]
|
||||||
|
("", Token::EOF),
|
||||||
|
("try", Token::Try),
|
||||||
|
("true", Token::True),
|
||||||
|
("break", Token::Break),
|
||||||
|
("return", Token::Return),
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
("fn", Token::Fn),
|
||||||
|
#[cfg(feature = "no_function")]
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
("import", Token::Import),
|
||||||
|
#[cfg(feature = "no_module")]
|
||||||
|
("", Token::EOF),
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
("?.", Token::Elvis),
|
||||||
|
#[cfg(feature = "no_object")]
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
("export", Token::Export),
|
||||||
|
#[cfg(feature = "no_module")]
|
||||||
|
("", Token::EOF),
|
||||||
|
("in", Token::In),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("(", Token::LeftParen),
|
||||||
|
("||", Token::Or),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("^", Token::XOr),
|
||||||
|
("^=", Token::XOrAssign),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("_", Token::Underscore),
|
||||||
|
("::", Token::DoubleColon),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("-", Token::Minus),
|
||||||
|
("-=", Token::MinusAssign),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("]", Token::RightBracket),
|
||||||
|
("()", Token::Unit),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("&", Token::Ampersand),
|
||||||
|
("&=", Token::AndAssign),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("%", Token::Modulo),
|
||||||
|
("%=", Token::ModuloAssign),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("!", Token::Bang),
|
||||||
|
("!=", Token::NotEqualsTo),
|
||||||
|
("!in", Token::NotIn),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("[", Token::LeftBracket),
|
||||||
|
("if", Token::If),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
(",Token::", Token::Comma),
|
||||||
|
("do", Token::Do),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
("as", Token::As),
|
||||||
|
#[cfg(feature = "no_module")]
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
("?[", Token::QuestionBracket),
|
||||||
|
#[cfg(feature = "no_index")]
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("??", Token::DoubleQuestion),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("&&", Token::And),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("", Token::EOF),
|
||||||
|
("#{", Token::MapStart),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Table-driven reserved symbol recognizer generated by GNU gperf on the file `tools/reserved.txt`.
|
||||||
|
//
|
||||||
|
// When adding new reserved symbols, make sure to update `tools/reserved.txt` and re-generate this.
|
||||||
|
|
||||||
|
const MIN_RESERVED_LEN: usize = 1;
|
||||||
|
const MAX_RESERVED_LEN: usize = 10;
|
||||||
|
const MIN_RESERVED_HASH_VALUE: usize = 1;
|
||||||
|
const MAX_RESERVED_HASH_VALUE: usize = 112;
|
||||||
|
|
||||||
|
static RESERVED_ASSOC_VALUES: [u8; 256] = [
|
||||||
|
113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113,
|
||||||
|
113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 35, 113, 45, 25, 113,
|
||||||
|
113, 113, 60, 55, 50, 50, 113, 15, 0, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113,
|
||||||
|
10, 85, 45, 5, 55, 50, 5, 113, 113, 113, 113, 113, 85, 113, 113, 113, 113, 113, 113, 113, 113,
|
||||||
|
113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 35, 113, 113, 113, 55, 113, 10, 40,
|
||||||
|
5, 0, 5, 35, 10, 5, 0, 113, 113, 20, 25, 5, 45, 0, 113, 0, 0, 0, 15, 30, 20, 25, 20, 113, 113,
|
||||||
|
20, 113, 0, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113,
|
||||||
|
113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113,
|
||||||
|
113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113,
|
||||||
|
113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113,
|
||||||
|
113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113,
|
||||||
|
113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113,
|
||||||
|
113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113,
|
||||||
|
];
|
||||||
|
static RESERVED_LIST: [(&str, bool, bool, bool); 113] = [
|
||||||
|
("", false, false, false),
|
||||||
|
("~", true, false, false),
|
||||||
|
("is", true, false, false),
|
||||||
|
("...", true, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("print", true, true, false),
|
||||||
|
("@", true, false, false),
|
||||||
|
("private", cfg!(feature = "no_function"), false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("this", true, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("thread", true, false, false),
|
||||||
|
("as", cfg!(feature = "no_module"), false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("spawn", true, false, false),
|
||||||
|
("static", true, false, false),
|
||||||
|
(":=", true, false, false),
|
||||||
|
("===", true, false, false),
|
||||||
|
("case", true, false, false),
|
||||||
|
("super", true, false, false),
|
||||||
|
("shared", true, false, false),
|
||||||
|
("package", true, false, false),
|
||||||
|
("use", true, false, false),
|
||||||
|
("with", true, false, false),
|
||||||
|
("curry", true, true, true),
|
||||||
|
("$", true, false, false),
|
||||||
|
("type_of", true, true, true),
|
||||||
|
("nil", true, false, false),
|
||||||
|
("sync", true, false, false),
|
||||||
|
("yield", true, false, false),
|
||||||
|
("import", cfg!(feature = "no_module"), false, false),
|
||||||
|
("--", true, false, false),
|
||||||
|
("new", true, false, false),
|
||||||
|
("exit", true, false, false),
|
||||||
|
("async", true, false, false),
|
||||||
|
("export", cfg!(feature = "no_module"), false, false),
|
||||||
|
("!.", true, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("call", true, true, true),
|
||||||
|
("match", true, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("fn", cfg!(feature = "no_function"), false, false),
|
||||||
|
("var", true, false, false),
|
||||||
|
("null", true, false, false),
|
||||||
|
("await", true, false, false),
|
||||||
|
("#", true, false, false),
|
||||||
|
("default", true, false, false),
|
||||||
|
("!==", true, false, false),
|
||||||
|
("eval", true, true, false),
|
||||||
|
("debug", true, true, false),
|
||||||
|
("?", true, false, false),
|
||||||
|
("?.", cfg!(feature = "no_object"), false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("protected", true, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("go", true, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("goto", true, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("public", true, false, false),
|
||||||
|
("<-", true, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("is_def_fn", cfg!(not(feature = "no_function")), true, false),
|
||||||
|
("is_def_var", true, true, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("<|", true, false, false),
|
||||||
|
("::<", true, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("->", true, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("module", true, false, false),
|
||||||
|
("|>", true, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("void", true, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("#!", true, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("?[", cfg!(feature = "no_index"), false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("Fn", true, true, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
(":;", true, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("++", true, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("*)", true, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("", false, false, false),
|
||||||
|
("(*", true, false, false),
|
||||||
|
];
|
||||||
|
|
||||||
impl Token {
|
impl Token {
|
||||||
/// Is the token a literal symbol?
|
/// Is the token a literal symbol?
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@ -529,101 +868,38 @@ impl Token {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Reverse lookup a symbol token from a piece of syntax.
|
/// Reverse lookup a symbol token from a piece of syntax.
|
||||||
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn lookup_symbol_from_syntax(syntax: &str) -> Option<Self> {
|
pub fn lookup_symbol_from_syntax(syntax: &str) -> Option<Self> {
|
||||||
#[allow(clippy::enum_glob_use)]
|
// This implementation is based upon a pre-calculated table generated
|
||||||
use Token::*;
|
// by GNU gperf on the list of keywords.
|
||||||
|
let utf8 = syntax.as_bytes();
|
||||||
|
let len = utf8.len();
|
||||||
|
let mut hash_val = len;
|
||||||
|
|
||||||
Some(match syntax {
|
if !(MIN_KEYWORD_LEN..=MAX_KEYWORD_LEN).contains(&len) {
|
||||||
"{" => LeftBrace,
|
return None;
|
||||||
"}" => RightBrace,
|
}
|
||||||
"(" => LeftParen,
|
|
||||||
")" => RightParen,
|
|
||||||
"[" => LeftBracket,
|
|
||||||
"]" => RightBracket,
|
|
||||||
"()" => Unit,
|
|
||||||
"+" => Plus,
|
|
||||||
"-" => Minus,
|
|
||||||
"*" => Multiply,
|
|
||||||
"/" => Divide,
|
|
||||||
";" => SemiColon,
|
|
||||||
":" => Colon,
|
|
||||||
"::" => DoubleColon,
|
|
||||||
"=>" => DoubleArrow,
|
|
||||||
"_" => Underscore,
|
|
||||||
"," => Comma,
|
|
||||||
"." => Period,
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
|
||||||
"?." => Elvis,
|
|
||||||
"??" => DoubleQuestion,
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
|
||||||
"?[" => QuestionBracket,
|
|
||||||
".." => ExclusiveRange,
|
|
||||||
"..=" => InclusiveRange,
|
|
||||||
"#{" => MapStart,
|
|
||||||
"=" => Equals,
|
|
||||||
"true" => True,
|
|
||||||
"false" => False,
|
|
||||||
"let" => Let,
|
|
||||||
"const" => Const,
|
|
||||||
"if" => If,
|
|
||||||
"else" => Else,
|
|
||||||
"switch" => Switch,
|
|
||||||
"do" => Do,
|
|
||||||
"while" => While,
|
|
||||||
"until" => Until,
|
|
||||||
"loop" => Loop,
|
|
||||||
"for" => For,
|
|
||||||
"in" => In,
|
|
||||||
"!in" => NotIn,
|
|
||||||
"<" => LessThan,
|
|
||||||
">" => GreaterThan,
|
|
||||||
"!" => Bang,
|
|
||||||
"<=" => LessThanEqualsTo,
|
|
||||||
">=" => GreaterThanEqualsTo,
|
|
||||||
"==" => EqualsTo,
|
|
||||||
"!=" => NotEqualsTo,
|
|
||||||
"|" => Pipe,
|
|
||||||
"||" => Or,
|
|
||||||
"&" => Ampersand,
|
|
||||||
"&&" => And,
|
|
||||||
"continue" => Continue,
|
|
||||||
"break" => Break,
|
|
||||||
"return" => Return,
|
|
||||||
"throw" => Throw,
|
|
||||||
"try" => Try,
|
|
||||||
"catch" => Catch,
|
|
||||||
"+=" => PlusAssign,
|
|
||||||
"-=" => MinusAssign,
|
|
||||||
"*=" => MultiplyAssign,
|
|
||||||
"/=" => DivideAssign,
|
|
||||||
"<<=" => LeftShiftAssign,
|
|
||||||
">>=" => RightShiftAssign,
|
|
||||||
"&=" => AndAssign,
|
|
||||||
"|=" => OrAssign,
|
|
||||||
"^=" => XOrAssign,
|
|
||||||
"<<" => LeftShift,
|
|
||||||
">>" => RightShift,
|
|
||||||
"^" => XOr,
|
|
||||||
"%" => Modulo,
|
|
||||||
"%=" => ModuloAssign,
|
|
||||||
"**" => PowerOf,
|
|
||||||
"**=" => PowerOfAssign,
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
match len {
|
||||||
"fn" => Fn,
|
1 => (),
|
||||||
#[cfg(not(feature = "no_function"))]
|
_ => hash_val += KEYWORD_ASSOC_VALUES[(utf8[1] as usize) + 1] as usize,
|
||||||
"private" => Private,
|
}
|
||||||
|
hash_val += KEYWORD_ASSOC_VALUES[utf8[0] as usize] as usize;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
if !(MIN_KEYWORD_HASH_VALUE..=MAX_KEYWORD_HASH_VALUE).contains(&hash_val) {
|
||||||
"import" => Import,
|
return None;
|
||||||
#[cfg(not(feature = "no_module"))]
|
}
|
||||||
"export" => Export,
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
"as" => As,
|
|
||||||
|
|
||||||
_ => return None,
|
match KEYWORDS_LIST[hash_val] {
|
||||||
})
|
(_, Token::EOF) => None,
|
||||||
|
// Fail early to avoid calling memcmp()
|
||||||
|
// Since we are already working with bytes, mind as well check the first one
|
||||||
|
(s, ref t) if s.len() == len && s.as_bytes()[0] == utf8[0] && s == syntax => {
|
||||||
|
Some(t.clone())
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If another operator is after these, it's probably a unary operator
|
/// If another operator is after these, it's probably a unary operator
|
||||||
@ -932,7 +1208,7 @@ pub fn parse_string_literal(
|
|||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
assert!(
|
debug_assert!(
|
||||||
!verbatim || escape.is_empty(),
|
!verbatim || escape.is_empty(),
|
||||||
"verbatim strings should not have any escapes"
|
"verbatim strings should not have any escapes"
|
||||||
);
|
);
|
||||||
@ -1229,11 +1505,7 @@ fn get_next_token_inner(
|
|||||||
// Still inside a comment?
|
// Still inside a comment?
|
||||||
if state.comment_level > 0 {
|
if state.comment_level > 0 {
|
||||||
let start_pos = *pos;
|
let start_pos = *pos;
|
||||||
let mut comment = if state.include_comments {
|
let mut comment = state.include_comments.then(|| String::new());
|
||||||
Some(String::new())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
state.comment_level =
|
state.comment_level =
|
||||||
scan_block_comment(stream, state.comment_level, pos, comment.as_mut());
|
scan_block_comment(stream, state.comment_level, pos, comment.as_mut());
|
||||||
@ -1273,8 +1545,10 @@ fn get_next_token_inner(
|
|||||||
pos.advance();
|
pos.advance();
|
||||||
|
|
||||||
let start_pos = *pos;
|
let start_pos = *pos;
|
||||||
|
let cc = stream.peek_next().unwrap_or('\0');
|
||||||
|
|
||||||
match (c, stream.peek_next().unwrap_or('\0')) {
|
// Identifiers and strings that can have non-ASCII characters
|
||||||
|
match (c, cc) {
|
||||||
// \n
|
// \n
|
||||||
('\n', ..) => pos.new_line(),
|
('\n', ..) => pos.new_line(),
|
||||||
|
|
||||||
@ -1438,16 +1712,6 @@ fn get_next_token_inner(
|
|||||||
return Some((token, num_pos));
|
return Some((token, num_pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
// letter or underscore ...
|
|
||||||
#[cfg(not(feature = "unicode-xid-ident"))]
|
|
||||||
('a'..='z' | '_' | 'A'..='Z', ..) => {
|
|
||||||
return Some(parse_identifier_token(stream, state, pos, start_pos, c));
|
|
||||||
}
|
|
||||||
#[cfg(feature = "unicode-xid-ident")]
|
|
||||||
(ch, ..) if unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_' => {
|
|
||||||
return Some(parse_identifier_token(stream, state, pos, start_pos, c));
|
|
||||||
}
|
|
||||||
|
|
||||||
// " - string literal
|
// " - string literal
|
||||||
('"', ..) => {
|
('"', ..) => {
|
||||||
return parse_string_literal(stream, state, pos, c, false, true, false)
|
return parse_string_literal(stream, state, pos, c, false, true, false)
|
||||||
@ -1919,11 +2183,16 @@ fn get_next_token_inner(
|
|||||||
}
|
}
|
||||||
('?', ..) => return Some((Token::Reserved(Box::new("?".into())), start_pos)),
|
('?', ..) => return Some((Token::Reserved(Box::new("?".into())), start_pos)),
|
||||||
|
|
||||||
(ch, ..) if ch.is_whitespace() => (),
|
// letter or underscore ...
|
||||||
|
_ if is_id_first_alphabetic(c) || c == '_' => {
|
||||||
|
return Some(parse_identifier_token(stream, state, pos, start_pos, c));
|
||||||
|
}
|
||||||
|
|
||||||
(ch, ..) => {
|
_ if c.is_whitespace() => (),
|
||||||
|
|
||||||
|
_ => {
|
||||||
return Some((
|
return Some((
|
||||||
Token::LexError(LERR::UnexpectedInput(ch.to_string()).into()),
|
Token::LexError(LERR::UnexpectedInput(c.to_string()).into()),
|
||||||
start_pos,
|
start_pos,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -1967,7 +2236,7 @@ fn parse_identifier_token(
|
|||||||
return (token, start_pos);
|
return (token, start_pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_reserved_keyword_or_symbol(&identifier) {
|
if is_reserved_keyword_or_symbol(&identifier).0 {
|
||||||
return (Token::Reserved(Box::new(identifier)), start_pos);
|
return (Token::Reserved(Box::new(identifier)), start_pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1981,30 +2250,6 @@ fn parse_identifier_token(
|
|||||||
(Token::Identifier(identifier.into()), start_pos)
|
(Token::Identifier(identifier.into()), start_pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Can a keyword be called like a function?
|
|
||||||
///
|
|
||||||
/// # Return values
|
|
||||||
///
|
|
||||||
/// The first `bool` indicates whether the keyword can be called normally as a function.
|
|
||||||
///
|
|
||||||
/// The second `bool` indicates whether the keyword can be called in method-call style.
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
pub fn is_keyword_function(name: &str) -> (bool, bool) {
|
|
||||||
match name {
|
|
||||||
KEYWORD_TYPE_OF | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY => (true, true),
|
|
||||||
|
|
||||||
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_EVAL | KEYWORD_FN_PTR | KEYWORD_IS_DEF_VAR => {
|
|
||||||
(true, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
crate::engine::KEYWORD_IS_DEF_FN => (true, false),
|
|
||||||
|
|
||||||
_ => (false, false),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// _(internals)_ Is a text string a valid identifier?
|
/// _(internals)_ Is a text string a valid identifier?
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@ -2030,72 +2275,71 @@ pub fn is_valid_identifier(name: &str) -> bool {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_valid_function_name(name: &str) -> bool {
|
pub fn is_valid_function_name(name: &str) -> bool {
|
||||||
is_valid_identifier(name)
|
is_valid_identifier(name)
|
||||||
&& !is_reserved_keyword_or_symbol(name)
|
&& !is_reserved_keyword_or_symbol(name).0
|
||||||
&& Token::lookup_symbol_from_syntax(name).is_none()
|
&& Token::lookup_symbol_from_syntax(name).is_none()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is a character valid to start an identifier?
|
/// Is a character valid to start an identifier?
|
||||||
#[cfg(feature = "unicode-xid-ident")]
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_id_first_alphabetic(x: char) -> bool {
|
pub fn is_id_first_alphabetic(x: char) -> bool {
|
||||||
unicode_xid::UnicodeXID::is_xid_start(x)
|
#[cfg(feature = "unicode-xid-ident")]
|
||||||
|
return unicode_xid::UnicodeXID::is_xid_start(x);
|
||||||
|
#[cfg(not(feature = "unicode-xid-ident"))]
|
||||||
|
return x.is_ascii_alphabetic();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is a character valid for an identifier?
|
/// Is a character valid for an identifier?
|
||||||
#[cfg(feature = "unicode-xid-ident")]
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_id_continue(x: char) -> bool {
|
pub fn is_id_continue(x: char) -> bool {
|
||||||
unicode_xid::UnicodeXID::is_xid_continue(x)
|
#[cfg(feature = "unicode-xid-ident")]
|
||||||
}
|
return unicode_xid::UnicodeXID::is_xid_continue(x);
|
||||||
|
|
||||||
/// Is a character valid to start an identifier?
|
|
||||||
#[cfg(not(feature = "unicode-xid-ident"))]
|
#[cfg(not(feature = "unicode-xid-ident"))]
|
||||||
#[inline(always)]
|
return x.is_ascii_alphanumeric() || x == '_';
|
||||||
#[must_use]
|
|
||||||
pub const fn is_id_first_alphabetic(x: char) -> bool {
|
|
||||||
x.is_ascii_alphabetic()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is a character valid for an identifier?
|
/// Is a piece of syntax a reserved keyword or reserved symbol?
|
||||||
#[cfg(not(feature = "unicode-xid-ident"))]
|
///
|
||||||
#[inline(always)]
|
/// # Return values
|
||||||
|
///
|
||||||
|
/// The first `bool` indicates whether it is a reserved keyword or symbol.
|
||||||
|
///
|
||||||
|
/// The second `bool` indicates whether the keyword can be called normally as a function.
|
||||||
|
///
|
||||||
|
/// The third `bool` indicates whether the keyword can be called in method-call style.
|
||||||
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn is_id_continue(x: char) -> bool {
|
pub fn is_reserved_keyword_or_symbol(syntax: &str) -> (bool, bool, bool) {
|
||||||
x.is_ascii_alphanumeric() || x == '_'
|
// This implementation is based upon a pre-calculated table generated
|
||||||
|
// by GNU gperf on the list of keywords.
|
||||||
|
let utf8 = syntax.as_bytes();
|
||||||
|
let len = utf8.len();
|
||||||
|
let rounds = len.min(3);
|
||||||
|
let mut hash_val = len;
|
||||||
|
|
||||||
|
if !(MIN_RESERVED_LEN..=MAX_RESERVED_LEN).contains(&len) {
|
||||||
|
return (false, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is a piece of syntax a reserved keyword or symbol?
|
for x in 0..rounds {
|
||||||
#[must_use]
|
hash_val += RESERVED_ASSOC_VALUES[utf8[rounds - 1 - x] as usize] as usize;
|
||||||
pub fn is_reserved_keyword_or_symbol(syntax: &str) -> bool {
|
}
|
||||||
match syntax {
|
|
||||||
#[cfg(feature = "no_object")]
|
|
||||||
"?." => true,
|
|
||||||
#[cfg(feature = "no_index")]
|
|
||||||
"?[" => true,
|
|
||||||
#[cfg(feature = "no_function")]
|
|
||||||
"fn" | "private" => true,
|
|
||||||
#[cfg(feature = "no_module")]
|
|
||||||
"import" | "export" | "as" => true,
|
|
||||||
|
|
||||||
// List of reserved operators
|
if !(MIN_RESERVED_HASH_VALUE..=MAX_RESERVED_HASH_VALUE).contains(&hash_val) {
|
||||||
"===" | "!==" | "->" | "<-" | "?" | ":=" | ":;" | "~" | "!." | "::<" | "(*" | "*)"
|
return (false, false, false);
|
||||||
| "#" | "#!" | "@" | "$" | "++" | "--" | "..." | "<|" | "|>" => true,
|
}
|
||||||
|
|
||||||
// List of reserved keywords
|
match RESERVED_LIST[hash_val] {
|
||||||
"public" | "protected" | "super" | "new" | "use" | "module" | "package" | "var"
|
("", ..) => (false, false, false),
|
||||||
| "static" | "shared" | "with" | "is" | "goto" | "exit" | "match" | "case" | "default"
|
(s, true, a, b) => (
|
||||||
| "void" | "null" | "nil" | "spawn" | "thread" | "go" | "sync" | "async" | "await"
|
// Fail early to avoid calling memcmp()
|
||||||
| "yield" => true,
|
// Since we are already working with bytes, mind as well check the first one
|
||||||
|
s.len() == len && s.as_bytes()[0] == utf8[0] && s == syntax,
|
||||||
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
a,
|
||||||
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS | KEYWORD_IS_DEF_VAR => true,
|
b,
|
||||||
|
),
|
||||||
#[cfg(not(feature = "no_function"))]
|
_ => (false, false, false),
|
||||||
crate::engine::KEYWORD_IS_DEF_FN => true,
|
|
||||||
|
|
||||||
_ => false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
7
src/tools/README.md
Normal file
7
src/tools/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Build Tools
|
||||||
|
===========
|
||||||
|
|
||||||
|
| File | Description |
|
||||||
|
| -------------- | ------------------------------------------- |
|
||||||
|
| `keywords.txt` | Input file for GNU gperf for the tokenizer. |
|
||||||
|
| `reserved.txt` | Input file for GNU gperf for the tokenizer. |
|
102
src/tools/keywords.txt
Normal file
102
src/tools/keywords.txt
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
// This file holds a list of keywords/symbols for the Rhai language, with mapping to
|
||||||
|
// an appropriate `Token` variant.
|
||||||
|
//
|
||||||
|
// Generate the output table via:
|
||||||
|
// ```bash
|
||||||
|
// gperf -t keywords.txt
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// Since GNU gperf does not produce Rust output, the ANSI-C output must be hand-edited and
|
||||||
|
// manually spliced into `tokenizer.rs`.
|
||||||
|
//
|
||||||
|
// This includes:
|
||||||
|
// * Rewrite the C hashing program (especially since it uses a `switch` statement with fall-through)
|
||||||
|
// into equivalent Rust as the function `lookup_symbol_from_syntax`.
|
||||||
|
// * Update the values for the `???_KEYWORD_???` constants.
|
||||||
|
// * Copy the `asso_values` array into `KEYWORD_ASSOC_VALUES`.
|
||||||
|
// * Copy the `wordlist` array into `KEYWORDS_LIST` with the following modifications:
|
||||||
|
// - Remove the `#line` comments
|
||||||
|
// - Change the entry wrapping `{ .. }` into tuples `( .. )`
|
||||||
|
// - Replace all entries `("")` by `("", Token::EOF)`
|
||||||
|
// - Put feature flags on the appropriate lines, and duplicating lines that maps to `Token::EOF`
|
||||||
|
// for the opposite feature flags
|
||||||
|
//
|
||||||
|
struct keyword;
|
||||||
|
%%
|
||||||
|
"{", Token::LeftBrace
|
||||||
|
"}", Token::RightBrace
|
||||||
|
"(", Token::LeftParen
|
||||||
|
")", Token::RightParen
|
||||||
|
"[", Token::LeftBracket
|
||||||
|
"]", Token::RightBracket
|
||||||
|
"()", Token::Unit
|
||||||
|
"+", Token::Plus
|
||||||
|
"-", Token::Minus
|
||||||
|
"*", Token::Multiply
|
||||||
|
"/", Token::Divide
|
||||||
|
";", Token::SemiColon
|
||||||
|
":", Token::Colon
|
||||||
|
"::", Token::DoubleColon
|
||||||
|
"=>", Token::DoubleArrow
|
||||||
|
"_", Token::Underscore
|
||||||
|
",", Token::Comma
|
||||||
|
".", Token::Period
|
||||||
|
"?.", Token::Elvis
|
||||||
|
"??", Token::DoubleQuestion
|
||||||
|
"?[", Token::QuestionBracket
|
||||||
|
"..", Token::ExclusiveRange
|
||||||
|
"..=", Token::InclusiveRange
|
||||||
|
"#{", Token::MapStart
|
||||||
|
"=", Token::Equals
|
||||||
|
"true", Token::True
|
||||||
|
"false", Token::False
|
||||||
|
"let", Token::Let
|
||||||
|
"const", Token::Const
|
||||||
|
"if", Token::If
|
||||||
|
"else", Token::Else
|
||||||
|
"switch", Token::Switch
|
||||||
|
"do", Token::Do
|
||||||
|
"while", Token::While
|
||||||
|
"until", Token::Until
|
||||||
|
"loop", Token::Loop
|
||||||
|
"for", Token::For
|
||||||
|
"in", Token::In
|
||||||
|
"!in", Token::NotIn
|
||||||
|
"<", Token::LessThan
|
||||||
|
">", Token::GreaterThan
|
||||||
|
"<=", Token::LessThanEqualsTo
|
||||||
|
">=", Token::GreaterThanEqualsTo
|
||||||
|
"==", Token::EqualsTo
|
||||||
|
"!=", Token::NotEqualsTo
|
||||||
|
"!", Token::Bang
|
||||||
|
"|", Token::Pipe
|
||||||
|
"||", Token::Or
|
||||||
|
"&", Token::Ampersand
|
||||||
|
"&&", Token::And
|
||||||
|
"continue", Token::Continue
|
||||||
|
"break", Token::Break
|
||||||
|
"return", Token::Return
|
||||||
|
"throw", Token::Throw
|
||||||
|
"try", Token::Try
|
||||||
|
"catch", Token::Catch
|
||||||
|
"+=", Token::PlusAssign
|
||||||
|
"-=", Token::MinusAssign
|
||||||
|
"*=", Token::MultiplyAssign
|
||||||
|
"/=", Token::DivideAssign
|
||||||
|
"<<=", Token::LeftShiftAssign
|
||||||
|
">>=", Token::RightShiftAssign
|
||||||
|
"&=", Token::AndAssign
|
||||||
|
"|=", Token::OrAssign
|
||||||
|
"^=", Token::XOrAssign
|
||||||
|
"<<", Token::LeftShift
|
||||||
|
">>", Token::RightShift
|
||||||
|
"^", Token::XOr
|
||||||
|
"%", Token::Modulo
|
||||||
|
"%=", Token::ModuloAssign
|
||||||
|
"**", Token::PowerOf
|
||||||
|
"**=", Token::PowerOfAssign
|
||||||
|
"fn", Token::Fn
|
||||||
|
"private", Token::Private
|
||||||
|
"import", Token::Import
|
||||||
|
"export", Token::Export
|
||||||
|
"as", Token::As
|
93
src/tools/reserved.txt
Normal file
93
src/tools/reserved.txt
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
// This file holds a list of reserved symbols for the Rhai language.
|
||||||
|
//
|
||||||
|
// The mapped attributes are:
|
||||||
|
// - is this a reserved symbol? (bool)
|
||||||
|
// - can this keyword be called normally as a function? (bool)
|
||||||
|
// - can this keyword be called in method-call style? (bool)
|
||||||
|
//
|
||||||
|
// Generate the output table via:
|
||||||
|
// ```bash
|
||||||
|
// gperf -t reserved.txt
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// Since GNU gperf does not produce Rust output, the ANSI-C output must be hand-edited and
|
||||||
|
// manually spliced into `tokenizer.rs`.
|
||||||
|
//
|
||||||
|
// This includes:
|
||||||
|
// * Rewrite the C hashing program (especially since it uses a `switch` statement with fall-through)
|
||||||
|
// into equivalent Rust as the function `is_reserved_keyword_or_symbol`.
|
||||||
|
// * Update the values for the `???_RESERVED_???` constants.
|
||||||
|
// * Copy the `asso_values` array into `RESERVED_ASSOC_VALUES`.
|
||||||
|
// * Copy the `wordlist` array into `RESERVED_LIST` with the following modifications:
|
||||||
|
// - Remove the `#line` comments
|
||||||
|
// - Change the entry wrapping `{ .. }` into tuples `( .. )`
|
||||||
|
// - Replace all entries `("")` by `("", false, false, false)`
|
||||||
|
// - Feature flags can be incorporated directly into the output via the `cfg!` macro
|
||||||
|
//
|
||||||
|
struct reserved;
|
||||||
|
%%
|
||||||
|
"?.", cfg!(feature = "no_object"), false, false
|
||||||
|
"?[", cfg!(feature = "no_index"), false, false
|
||||||
|
"fn", cfg!(feature = "no_function"), false, false
|
||||||
|
"private", cfg!(feature = "no_function"), false, false
|
||||||
|
"import", cfg!(feature = "no_module"), false, false
|
||||||
|
"export", cfg!(feature = "no_module"), false, false
|
||||||
|
"as", cfg!(feature = "no_module"), false, false
|
||||||
|
"===", true, false, false
|
||||||
|
"!==", true, false, false
|
||||||
|
"->", true, false, false
|
||||||
|
"<-", true, false, false
|
||||||
|
"?", true, false, false
|
||||||
|
":=", true, false, false
|
||||||
|
":;", true, false, false
|
||||||
|
"~", true, false, false
|
||||||
|
"!.", true, false, false
|
||||||
|
"::<", true, false, false
|
||||||
|
"(*", true, false, false
|
||||||
|
"*)", true, false, false
|
||||||
|
"#", true, false, false
|
||||||
|
"#!", true, false, false
|
||||||
|
"@", true, false, false
|
||||||
|
"$", true, false, false
|
||||||
|
"++", true, false, false
|
||||||
|
"--", true, false, false
|
||||||
|
"...", true, false, false
|
||||||
|
"<|", true, false, false
|
||||||
|
"|>", true, false, false
|
||||||
|
"public", true, false, false
|
||||||
|
"protected", true, false, false
|
||||||
|
"super", true, false, false
|
||||||
|
"new", true, false, false
|
||||||
|
"use", true, false, false
|
||||||
|
"module", true, false, false
|
||||||
|
"package", true, false, false
|
||||||
|
"var", true, false, false
|
||||||
|
"static", true, false, false
|
||||||
|
"shared", true, false, false
|
||||||
|
"with", true, false, false
|
||||||
|
"is", true, false, false
|
||||||
|
"goto", true, false, false
|
||||||
|
"exit", true, false, false
|
||||||
|
"match", true, false, false
|
||||||
|
"case", true, false, false
|
||||||
|
"default", true, false, false
|
||||||
|
"void", true, false, false
|
||||||
|
"null", true, false, false
|
||||||
|
"nil", true, false, false
|
||||||
|
"spawn", true, false, false
|
||||||
|
"thread", true, false, false
|
||||||
|
"go", true, false, false
|
||||||
|
"sync", true, false, false
|
||||||
|
"async", true, false, false
|
||||||
|
"await", true, false, false
|
||||||
|
"yield", true, false, false
|
||||||
|
"print", true, true, false
|
||||||
|
"debug", true, true, false
|
||||||
|
"type_of", true, true, true
|
||||||
|
"eval", true, true, false
|
||||||
|
"Fn", true, true, false
|
||||||
|
"call", true, true, true
|
||||||
|
"curry", true, true, true
|
||||||
|
"this", true, false, false
|
||||||
|
"is_def_var", true, true, false
|
||||||
|
"is_def_fn", cfg!(not(feature = "no_function")), true, false
|
@ -538,7 +538,7 @@ impl TryFrom<ImmutableString> for FnPtr {
|
|||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
fn_def: None,
|
fn_def: None,
|
||||||
})
|
})
|
||||||
} else if is_reserved_keyword_or_symbol(&value)
|
} else if is_reserved_keyword_or_symbol(&value).0
|
||||||
|| Token::lookup_symbol_from_syntax(&value).is_some()
|
|| Token::lookup_symbol_from_syntax(&value).is_some()
|
||||||
{
|
{
|
||||||
Err(
|
Err(
|
||||||
|
@ -105,7 +105,7 @@ pub enum ParseErrorType {
|
|||||||
DuplicatedSwitchCase,
|
DuplicatedSwitchCase,
|
||||||
/// A variable name is duplicated. Wrapped value is the variable name.
|
/// A variable name is duplicated. Wrapped value is the variable name.
|
||||||
DuplicatedVariable(String),
|
DuplicatedVariable(String),
|
||||||
/// An integer case of a `switch` statement is in an appropriate place.
|
/// A numeric case of a `switch` statement is in an appropriate place.
|
||||||
WrongSwitchIntegerCase,
|
WrongSwitchIntegerCase,
|
||||||
/// The default case of a `switch` statement is in an appropriate place.
|
/// The default case of a `switch` statement is in an appropriate place.
|
||||||
WrongSwitchDefaultCase,
|
WrongSwitchDefaultCase,
|
||||||
@ -236,7 +236,7 @@ impl fmt::Display for ParseErrorType {
|
|||||||
Self::Reserved(s) if is_valid_identifier(s.as_str()) => write!(f, "'{s}' is a reserved keyword"),
|
Self::Reserved(s) if is_valid_identifier(s.as_str()) => write!(f, "'{s}' is a reserved keyword"),
|
||||||
Self::Reserved(s) => write!(f, "'{s}' is a reserved symbol"),
|
Self::Reserved(s) => write!(f, "'{s}' is a reserved symbol"),
|
||||||
Self::UnexpectedEOF => f.write_str("Script is incomplete"),
|
Self::UnexpectedEOF => f.write_str("Script is incomplete"),
|
||||||
Self::WrongSwitchIntegerCase => f.write_str("Integer switch case cannot follow a range case"),
|
Self::WrongSwitchIntegerCase => f.write_str("Numeric switch case cannot follow a range case"),
|
||||||
Self::WrongSwitchDefaultCase => f.write_str("Default switch case must be the last"),
|
Self::WrongSwitchDefaultCase => f.write_str("Default switch case must be the last"),
|
||||||
Self::WrongSwitchCaseCondition => f.write_str("This switch case cannot have a condition"),
|
Self::WrongSwitchCaseCondition => f.write_str("This switch case cannot have a condition"),
|
||||||
Self::PropertyExpected => f.write_str("Expecting name of a property"),
|
Self::PropertyExpected => f.write_str("Expecting name of a property"),
|
||||||
|
@ -385,11 +385,23 @@ impl Scope<'_> {
|
|||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn pop(&mut self) -> &mut Self {
|
pub fn pop(&mut self) -> &mut Self {
|
||||||
self.names.pop().expect("`Scope` must not be empty");
|
self.names.pop().expect("not empty");
|
||||||
let _ = self.values.pop().expect("`Scope` must not be empty");
|
let _ = self.values.pop().expect("not empty");
|
||||||
self.aliases.pop().expect("`Scope` must not be empty");
|
self.aliases.pop().expect("not empty");
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
/// Remove the last entry from the [`Scope`] and return it.
|
||||||
|
#[inline(always)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn pop_entry(&mut self) -> Option<(Identifier, Dynamic, Vec<ImmutableString>)> {
|
||||||
|
self.values.pop().map(|value| {
|
||||||
|
(
|
||||||
|
self.names.pop().expect("not empty"),
|
||||||
|
value,
|
||||||
|
self.aliases.pop().expect("not empty"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
/// Truncate (rewind) the [`Scope`] to a previous size.
|
/// Truncate (rewind) the [`Scope`] to a previous size.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
|
@ -515,14 +515,12 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
|
|||||||
3
|
3
|
||||||
);
|
);
|
||||||
|
|
||||||
engine
|
engine.eval::<()>(
|
||||||
.eval::<()>(
|
|
||||||
"
|
"
|
||||||
let x = [1, 2, 3, 2, 1];
|
let x = [1, 2, 3, 2, 1];
|
||||||
x.find(|v| v > 4)
|
x.find(|v| v > 4)
|
||||||
",
|
",
|
||||||
)
|
)?;
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
@ -534,14 +532,12 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
|
|||||||
2
|
2
|
||||||
);
|
);
|
||||||
|
|
||||||
engine
|
engine.eval::<()>(
|
||||||
.eval::<()>(
|
|
||||||
"
|
"
|
||||||
let x = [#{alice: 1}, #{bob: 2}, #{clara: 3}];
|
let x = [#{alice: 1}, #{bob: 2}, #{clara: 3}];
|
||||||
x.find_map(|v| v.dave)
|
x.find_map(|v| v.dave)
|
||||||
",
|
",
|
||||||
)
|
)?;
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -550,7 +546,7 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
|
|||||||
fn test_arrays_elvis() -> Result<(), Box<EvalAltResult>> {
|
fn test_arrays_elvis() -> Result<(), Box<EvalAltResult>> {
|
||||||
let engine = Engine::new();
|
let engine = Engine::new();
|
||||||
|
|
||||||
engine.eval::<()>("let x = (); x?[2]").unwrap();
|
engine.eval::<()>("let x = (); x?[2]")?;
|
||||||
|
|
||||||
engine.run("let x = (); x?[2] = 42")?;
|
engine.run("let x = (); x?[2] = 42")?;
|
||||||
|
|
||||||
|
@ -4,8 +4,8 @@ use rhai::{Engine, EvalAltResult};
|
|||||||
fn test_bool_op1() -> Result<(), Box<EvalAltResult>> {
|
fn test_bool_op1() -> Result<(), Box<EvalAltResult>> {
|
||||||
let engine = Engine::new();
|
let engine = Engine::new();
|
||||||
|
|
||||||
assert!(engine.eval::<bool>("true && (false || true)").unwrap());
|
assert!(engine.eval::<bool>("true && (false || true)")?);
|
||||||
assert!(engine.eval::<bool>("true & (false | true)").unwrap());
|
assert!(engine.eval::<bool>("true & (false | true)")?);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -14,8 +14,8 @@ fn test_bool_op1() -> Result<(), Box<EvalAltResult>> {
|
|||||||
fn test_bool_op2() -> Result<(), Box<EvalAltResult>> {
|
fn test_bool_op2() -> Result<(), Box<EvalAltResult>> {
|
||||||
let engine = Engine::new();
|
let engine = Engine::new();
|
||||||
|
|
||||||
assert!(!engine.eval::<bool>("false && (false || true)").unwrap());
|
assert!(!engine.eval::<bool>("false && (false || true)")?);
|
||||||
assert!(!engine.eval::<bool>("false & (false | true)").unwrap());
|
assert!(!engine.eval::<bool>("false & (false | true)")?);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -25,9 +25,9 @@ fn test_bool_op3() -> Result<(), Box<EvalAltResult>> {
|
|||||||
let engine = Engine::new();
|
let engine = Engine::new();
|
||||||
|
|
||||||
assert!(engine.eval::<bool>("true && (false || 123)").is_err());
|
assert!(engine.eval::<bool>("true && (false || 123)").is_err());
|
||||||
assert!(engine.eval::<bool>("true && (true || { throw })").unwrap());
|
assert!(engine.eval::<bool>("true && (true || { throw })")?);
|
||||||
assert!(engine.eval::<bool>("123 && (false || true)").is_err());
|
assert!(engine.eval::<bool>("123 && (false || true)").is_err());
|
||||||
assert!(!engine.eval::<bool>("false && (true || { throw })").unwrap());
|
assert!(!engine.eval::<bool>("false && (true || { throw })")?);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -36,23 +36,19 @@ fn test_bool_op3() -> Result<(), Box<EvalAltResult>> {
|
|||||||
fn test_bool_op_short_circuit() -> Result<(), Box<EvalAltResult>> {
|
fn test_bool_op_short_circuit() -> Result<(), Box<EvalAltResult>> {
|
||||||
let engine = Engine::new();
|
let engine = Engine::new();
|
||||||
|
|
||||||
assert!(engine
|
assert!(engine.eval::<bool>(
|
||||||
.eval::<bool>(
|
|
||||||
"
|
"
|
||||||
let x = true;
|
let x = true;
|
||||||
x || { throw; };
|
x || { throw; };
|
||||||
"
|
"
|
||||||
)
|
)?);
|
||||||
.unwrap());
|
|
||||||
|
|
||||||
assert!(!engine
|
assert!(!engine.eval::<bool>(
|
||||||
.eval::<bool>(
|
|
||||||
"
|
"
|
||||||
let x = false;
|
let x = false;
|
||||||
x && { throw; };
|
x && { throw; };
|
||||||
"
|
"
|
||||||
)
|
)?);
|
||||||
.unwrap());
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -369,9 +369,9 @@ fn test_closures_external() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
let fn_ptr = engine.eval_ast::<FnPtr>(&ast)?;
|
let fn_ptr = engine.eval_ast::<FnPtr>(&ast)?;
|
||||||
|
|
||||||
let f = move |x: INT| -> String { fn_ptr.call(&engine, &ast, (x,)).unwrap() };
|
let f = move |x: INT| fn_ptr.call::<String>(&engine, &ast, (x,));
|
||||||
|
|
||||||
assert_eq!(f(42), "hello42");
|
assert_eq!(f(42)?, "hello42");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -383,20 +383,20 @@ fn test_closures_callback() -> Result<(), Box<EvalAltResult>> {
|
|||||||
type SingleNode = Rc<dyn Node>;
|
type SingleNode = Rc<dyn Node>;
|
||||||
|
|
||||||
trait Node {
|
trait Node {
|
||||||
fn run(&self, x: INT) -> INT;
|
fn run(&self, x: INT) -> Result<INT, Box<EvalAltResult>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PhaserNode {
|
struct PhaserNode {
|
||||||
func: Box<dyn Fn(INT) -> INT>,
|
func: Box<dyn Fn(INT) -> Result<INT, Box<EvalAltResult>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node for PhaserNode {
|
impl Node for PhaserNode {
|
||||||
fn run(&self, x: INT) -> INT {
|
fn run(&self, x: INT) -> Result<INT, Box<EvalAltResult>> {
|
||||||
(self.func)(x)
|
(self.func)(x)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn phaser(callback: impl Fn(INT) -> INT + 'static) -> impl Node {
|
fn phaser(callback: impl Fn(INT) -> Result<INT, Box<EvalAltResult>> + 'static) -> impl Node {
|
||||||
PhaserNode {
|
PhaserNode {
|
||||||
func: Box::new(callback),
|
func: Box::new(callback),
|
||||||
}
|
}
|
||||||
@ -419,7 +419,7 @@ fn test_closures_callback() -> Result<(), Box<EvalAltResult>> {
|
|||||||
let engine = engine2.clone();
|
let engine = engine2.clone();
|
||||||
let ast = ast2.clone();
|
let ast = ast2.clone();
|
||||||
|
|
||||||
let callback = Box::new(move |x: INT| fp.call(&engine.borrow(), &ast, (x,)).unwrap());
|
let callback = Box::new(move |x: INT| fp.call(&engine.borrow(), &ast, (x,)));
|
||||||
|
|
||||||
Rc::new(phaser(callback)) as SingleNode
|
Rc::new(phaser(callback)) as SingleNode
|
||||||
});
|
});
|
||||||
@ -428,7 +428,7 @@ fn test_closures_callback() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
let cb = shared_engine.borrow().eval_ast::<SingleNode>(&ast)?;
|
let cb = shared_engine.borrow().eval_ast::<SingleNode>(&ast)?;
|
||||||
|
|
||||||
assert_eq!(cb.run(21), 42);
|
assert_eq!(cb.run(21)?, 42);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ fn test_comments() -> Result<(), Box<EvalAltResult>> {
|
|||||||
42
|
42
|
||||||
);
|
);
|
||||||
|
|
||||||
engine.run("/* Hello world */").unwrap();
|
engine.run("/* Hello world */")?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,8 @@ fn test_or_equals() -> Result<(), Box<EvalAltResult>> {
|
|||||||
let engine = Engine::new();
|
let engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(engine.eval::<INT>("let x = 16; x |= 74; x")?, 90);
|
assert_eq!(engine.eval::<INT>("let x = 16; x |= 74; x")?, 90);
|
||||||
assert!(engine.eval::<bool>("let x = true; x |= false; x").unwrap());
|
assert!(engine.eval::<bool>("let x = true; x |= false; x")?);
|
||||||
assert!(engine.eval::<bool>("let x = false; x |= true; x").unwrap());
|
assert!(engine.eval::<bool>("let x = false; x |= true; x")?);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -16,9 +16,9 @@ fn test_and_equals() -> Result<(), Box<EvalAltResult>> {
|
|||||||
let engine = Engine::new();
|
let engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(engine.eval::<INT>("let x = 16; x &= 31; x")?, 16);
|
assert_eq!(engine.eval::<INT>("let x = 16; x &= 31; x")?, 16);
|
||||||
assert!(!engine.eval::<bool>("let x = true; x &= false; x").unwrap());
|
assert!(!engine.eval::<bool>("let x = true; x &= false; x")?);
|
||||||
assert!(!engine.eval::<bool>("let x = false; x &= true; x").unwrap());
|
assert!(!engine.eval::<bool>("let x = false; x &= true; x")?);
|
||||||
assert!(engine.eval::<bool>("let x = true; x &= true; x").unwrap());
|
assert!(engine.eval::<bool>("let x = true; x &= true; x")?);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -251,6 +251,62 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_custom_syntax_matrix() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
engine.disable_symbol("|");
|
||||||
|
|
||||||
|
engine.register_custom_syntax(
|
||||||
|
[
|
||||||
|
"@", //
|
||||||
|
"|", "$expr$", "$expr$", "$expr$", "|", //
|
||||||
|
"|", "$expr$", "$expr$", "$expr$", "|", //
|
||||||
|
"|", "$expr$", "$expr$", "$expr$", "|",
|
||||||
|
],
|
||||||
|
false,
|
||||||
|
|context, inputs| {
|
||||||
|
let mut values = [[0; 3]; 3];
|
||||||
|
|
||||||
|
for y in 0..3 {
|
||||||
|
for x in 0..3 {
|
||||||
|
let offset = y * 3 + x;
|
||||||
|
|
||||||
|
match context.eval_expression_tree(&inputs[offset])?.as_int() {
|
||||||
|
Ok(v) => values[y][x] = v,
|
||||||
|
Err(typ) => {
|
||||||
|
return Err(Box::new(EvalAltResult::ErrorMismatchDataType(
|
||||||
|
"integer".to_string(),
|
||||||
|
typ.to_string(),
|
||||||
|
inputs[offset].position(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Dynamic::from(values))
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let r = engine.eval::<[[INT; 3]; 3]>(
|
||||||
|
"
|
||||||
|
let a = 42;
|
||||||
|
let b = 123;
|
||||||
|
let c = 1;
|
||||||
|
let d = 99;
|
||||||
|
|
||||||
|
@| a b 0 |
|
||||||
|
| -b a 0 |
|
||||||
|
| 0 0 c*d |
|
||||||
|
",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_eq!(r, [[42, 123, 0], [-123, 42, 0], [0, 0, 99]]);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_custom_syntax_raw() -> Result<(), Box<EvalAltResult>> {
|
fn test_custom_syntax_raw() -> Result<(), Box<EvalAltResult>> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
@ -50,8 +50,8 @@ fn test_expressions() -> Result<(), Box<EvalAltResult>> {
|
|||||||
"
|
"
|
||||||
switch x {
|
switch x {
|
||||||
0 => 1,
|
0 => 1,
|
||||||
1..10 => 123,
|
|
||||||
10 => 42,
|
10 => 42,
|
||||||
|
1..10 => 123,
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
)?,
|
)?,
|
||||||
@ -63,11 +63,11 @@ fn test_expressions() -> Result<(), Box<EvalAltResult>> {
|
|||||||
"
|
"
|
||||||
switch x {
|
switch x {
|
||||||
0 => 1,
|
0 => 1,
|
||||||
|
10 => 42,
|
||||||
1..10 => {
|
1..10 => {
|
||||||
let y = 123;
|
let y = 123;
|
||||||
y
|
y
|
||||||
}
|
}
|
||||||
10 => 42,
|
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
)
|
)
|
||||||
|
@ -9,9 +9,7 @@ fn test_float() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
assert!(engine.eval::<bool>("let x = 0.0; let y = 1.0; x < y")?);
|
assert!(engine.eval::<bool>("let x = 0.0; let y = 1.0; x < y")?);
|
||||||
assert!(!engine.eval::<bool>("let x = 0.0; let y = 1.0; x > y")?);
|
assert!(!engine.eval::<bool>("let x = 0.0; let y = 1.0; x > y")?);
|
||||||
assert!(!engine
|
assert!(!engine.eval::<bool>("let x = 0.; let y = 1.; x > y")?);
|
||||||
.eval::<bool>("let x = 0.; let y = 1.; x > y")
|
|
||||||
.unwrap());
|
|
||||||
assert!((engine.eval::<FLOAT>("let x = 9.9999; x")? - 9.9999 as FLOAT).abs() < EPSILON);
|
assert!((engine.eval::<FLOAT>("let x = 9.9999; x")? - 9.9999 as FLOAT).abs() < EPSILON);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -399,11 +399,9 @@ fn test_get_set_indexer() -> Result<(), Box<EvalAltResult>> {
|
|||||||
fn test_get_set_elvis() -> Result<(), Box<EvalAltResult>> {
|
fn test_get_set_elvis() -> Result<(), Box<EvalAltResult>> {
|
||||||
let engine = Engine::new();
|
let engine = Engine::new();
|
||||||
|
|
||||||
engine.eval::<()>("let x = (); x?.foo.bar.baz").unwrap();
|
engine.eval::<()>("let x = (); x?.foo.bar.baz")?;
|
||||||
engine.eval::<()>("let x = (); x?.foo(1,2,3)").unwrap();
|
engine.eval::<()>("let x = (); x?.foo(1,2,3)")?;
|
||||||
engine
|
engine.eval::<()>("let x = #{a:()}; x.a?.foo.bar.baz")?;
|
||||||
.eval::<()>("let x = #{a:()}; x.a?.foo.bar.baz")
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(engine.eval::<String>("let x = 'x'; x?.type_of()")?, "char");
|
assert_eq!(engine.eval::<String>("let x = 'x'; x?.type_of()")?, "char");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -20,7 +20,7 @@ fn test_loop() -> Result<(), Box<EvalAltResult>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return x;
|
x
|
||||||
"
|
"
|
||||||
)?,
|
)?,
|
||||||
21
|
21
|
||||||
@ -53,3 +53,43 @@ fn test_loop() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_loop_expression() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
"
|
||||||
|
let x = 0;
|
||||||
|
|
||||||
|
let value = while x < 10 {
|
||||||
|
if x % 5 == 0 { break 42; }
|
||||||
|
x += 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
value
|
||||||
|
"
|
||||||
|
)?,
|
||||||
|
42
|
||||||
|
);
|
||||||
|
|
||||||
|
engine.set_allow_loop_expressions(false);
|
||||||
|
|
||||||
|
assert!(engine
|
||||||
|
.eval::<INT>(
|
||||||
|
"
|
||||||
|
let x = 0;
|
||||||
|
|
||||||
|
let value = while x < 10 {
|
||||||
|
if x % 5 == 0 { break 42; }
|
||||||
|
x += 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
value
|
||||||
|
"
|
||||||
|
)
|
||||||
|
.is_err());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
@ -39,7 +39,10 @@ fn test_mismatched_op_custom_type() -> Result<(), Box<EvalAltResult>> {
|
|||||||
").expect_err("should error"),
|
").expect_err("should error"),
|
||||||
EvalAltResult::ErrorFunctionNotFound(f, ..) if f == "== (TestStruct, TestStruct)"));
|
EvalAltResult::ErrorFunctionNotFound(f, ..) if f == "== (TestStruct, TestStruct)"));
|
||||||
|
|
||||||
assert!(!engine.eval::<bool>("new_ts() == 42")?);
|
assert!(
|
||||||
|
matches!(*engine.eval::<bool>("new_ts() == 42").expect_err("should error"),
|
||||||
|
EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("== (TestStruct, "))
|
||||||
|
);
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
*engine.eval::<INT>("60 + new_ts()").expect_err("should error"),
|
*engine.eval::<INT>("60 + new_ts()").expect_err("should error"),
|
||||||
|
@ -4,14 +4,12 @@ use rhai::{Engine, EvalAltResult};
|
|||||||
fn test_not() -> Result<(), Box<EvalAltResult>> {
|
fn test_not() -> Result<(), Box<EvalAltResult>> {
|
||||||
let engine = Engine::new();
|
let engine = Engine::new();
|
||||||
|
|
||||||
assert!(!engine
|
assert!(!engine.eval::<bool>("let not_true = !true; not_true")?);
|
||||||
.eval::<bool>("let not_true = !true; not_true")
|
|
||||||
.unwrap());
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
assert!(engine.eval::<bool>("fn not(x) { !x } not(false)").unwrap());
|
assert!(engine.eval::<bool>("fn not(x) { !x } not(false)")?);
|
||||||
|
|
||||||
assert!(engine.eval::<bool>("!!!!true").unwrap());
|
assert!(engine.eval::<bool>("!!!!true")?);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
27
tests/ops.rs
27
tests/ops.rs
@ -35,7 +35,11 @@ fn test_ops_other_number_types() -> Result<(), Box<EvalAltResult>> {
|
|||||||
EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("== (u16,")
|
EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("== (u16,")
|
||||||
));
|
));
|
||||||
|
|
||||||
assert!(!engine.eval_with_scope::<bool>(&mut scope, r#"x == "hello""#)?);
|
assert!(
|
||||||
|
matches!(*engine.eval_with_scope::<bool>(&mut scope, r#"x == "hello""#).expect_err("should error"),
|
||||||
|
EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("== (u16,")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -63,3 +67,24 @@ fn test_ops_precedence() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ops_custom_types() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
struct Test1;
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
struct Test2;
|
||||||
|
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
engine
|
||||||
|
.register_type_with_name::<Test1>("Test1")
|
||||||
|
.register_type_with_name::<Test2>("Test2")
|
||||||
|
.register_fn("new_ts1", || Test1)
|
||||||
|
.register_fn("new_ts2", || Test2)
|
||||||
|
.register_fn("==", |x: Test1, y: Test2| true);
|
||||||
|
|
||||||
|
assert!(engine.eval::<bool>("let x = new_ts1(); let y = new_ts2(); x == y")?);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
@ -89,21 +89,21 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
format!("{ast:?}"),
|
format!("{ast:?}"),
|
||||||
r#"AST { source: None, doc: "", resolver: None, body: [Expr(123 @ 1:53)] }"#
|
r#"AST { source: None, doc: None, resolver: None, body: [Expr(123 @ 1:53)] }"#
|
||||||
);
|
);
|
||||||
|
|
||||||
let ast = engine.compile("const DECISION = false; if DECISION { 42 } else { 123 }")?;
|
let ast = engine.compile("const DECISION = false; if DECISION { 42 } else { 123 }")?;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
format!("{ast:?}"),
|
format!("{ast:?}"),
|
||||||
r#"AST { source: None, doc: "", resolver: None, body: [Var(("DECISION" @ 1:7, false @ 1:18, None), CONSTANT, 1:1), Expr(123 @ 1:51)] }"#
|
r#"AST { source: None, doc: None, resolver: None, body: [Var(("DECISION" @ 1:7, false @ 1:18, None), CONSTANT, 1:1), Expr(123 @ 1:51)] }"#
|
||||||
);
|
);
|
||||||
|
|
||||||
let ast = engine.compile("if 1 == 2 { 42 }")?;
|
let ast = engine.compile("if 1 == 2 { 42 }")?;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
format!("{ast:?}"),
|
format!("{ast:?}"),
|
||||||
r#"AST { source: None, doc: "", resolver: None, body: [] }"#
|
r#"AST { source: None, doc: None, resolver: None, body: [] }"#
|
||||||
);
|
);
|
||||||
|
|
||||||
engine.set_optimization_level(OptimizationLevel::Full);
|
engine.set_optimization_level(OptimizationLevel::Full);
|
||||||
@ -112,14 +112,14 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
format!("{ast:?}"),
|
format!("{ast:?}"),
|
||||||
r#"AST { source: None, doc: "", resolver: None, body: [Expr(42 @ 1:1)] }"#
|
r#"AST { source: None, doc: None, resolver: None, body: [Expr(42 @ 1:1)] }"#
|
||||||
);
|
);
|
||||||
|
|
||||||
let ast = engine.compile("NUMBER")?;
|
let ast = engine.compile("NUMBER")?;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
format!("{ast:?}"),
|
format!("{ast:?}"),
|
||||||
r#"AST { source: None, doc: "", resolver: None, body: [Expr(Variable(NUMBER) @ 1:1)] }"#
|
r#"AST { source: None, doc: None, resolver: None, body: [Expr(Variable(NUMBER) @ 1:1)] }"#
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut module = Module::new();
|
let mut module = Module::new();
|
||||||
@ -131,7 +131,7 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
format!("{ast:?}"),
|
format!("{ast:?}"),
|
||||||
r#"AST { source: None, doc: "", resolver: None, body: [Expr(42 @ 1:1)] }"#
|
r#"AST { source: None, doc: None, resolver: None, body: [Expr(42 @ 1:1)] }"#
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -10,9 +10,7 @@ fn test_switch() -> Result<(), Box<EvalAltResult>> {
|
|||||||
engine.eval::<char>("switch 2 { 1 => (), 2 => 'a', 42 => true }")?,
|
engine.eval::<char>("switch 2 { 1 => (), 2 => 'a', 42 => true }")?,
|
||||||
'a'
|
'a'
|
||||||
);
|
);
|
||||||
engine
|
engine.run("switch 3 { 1 => (), 2 => 'a', 42 => true }")?;
|
||||||
.run("switch 3 { 1 => (), 2 => 'a', 42 => true }")
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>("switch 3 { 1 => (), 2 => 'a', 42 => true, _ => 123 }")?,
|
engine.eval::<INT>("switch 3 { 1 => (), 2 => 'a', 42 => true, _ => 123 }")?,
|
||||||
123
|
123
|
||||||
@ -31,15 +29,13 @@ fn test_switch() -> Result<(), Box<EvalAltResult>> {
|
|||||||
)?,
|
)?,
|
||||||
'a'
|
'a'
|
||||||
);
|
);
|
||||||
assert!(engine
|
assert!(
|
||||||
.eval_with_scope::<bool>(&mut scope, "switch x { 1 => (), 2 => 'a', 42 => true }")
|
engine.eval_with_scope::<bool>(&mut scope, "switch x { 1 => (), 2 => 'a', 42 => true }")?
|
||||||
.unwrap());
|
);
|
||||||
assert!(engine
|
assert!(
|
||||||
.eval_with_scope::<bool>(&mut scope, "switch x { 1 => (), 2 => 'a', _ => true }")
|
engine.eval_with_scope::<bool>(&mut scope, "switch x { 1 => (), 2 => 'a', _ => true }")?
|
||||||
.unwrap());
|
);
|
||||||
let _: () = engine
|
let _: () = engine.eval_with_scope::<()>(&mut scope, "switch x { 1 => 123, 2 => 'a' }")?;
|
||||||
.eval_with_scope::<()>(&mut scope, "switch x { 1 => 123, 2 => 'a' }")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval_with_scope::<INT>(
|
engine.eval_with_scope::<INT>(
|
||||||
@ -276,6 +272,13 @@ fn test_switch_ranges() -> Result<(), Box<EvalAltResult>> {
|
|||||||
).expect_err("should error").err_type(),
|
).expect_err("should error").err_type(),
|
||||||
ParseErrorType::WrongSwitchIntegerCase
|
ParseErrorType::WrongSwitchIntegerCase
|
||||||
));
|
));
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
assert!(matches!(
|
||||||
|
engine.compile(
|
||||||
|
"switch x { 10..20 => (), 20..=42 => 'a', 25..45 => 'z', 42.0 => 'x', 30..100 => true }"
|
||||||
|
).expect_err("should error").err_type(),
|
||||||
|
ParseErrorType::WrongSwitchIntegerCase
|
||||||
|
));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval_with_scope::<char>(
|
engine.eval_with_scope::<char>(
|
||||||
&mut scope,
|
&mut scope,
|
||||||
|
@ -10,9 +10,7 @@ fn test_unit() -> Result<(), Box<EvalAltResult>> {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_unit_eq() -> Result<(), Box<EvalAltResult>> {
|
fn test_unit_eq() -> Result<(), Box<EvalAltResult>> {
|
||||||
let engine = Engine::new();
|
let engine = Engine::new();
|
||||||
assert!(engine
|
assert!(engine.eval::<bool>("let x = (); let y = (); x == y")?);
|
||||||
.eval::<bool>("let x = (); let y = (); x == y")
|
|
||||||
.unwrap());
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user