Merge pull request #708 from schungx/master

Speed improvement attempts.
This commit is contained in:
Stephen Chung 2023-03-16 19:20:15 +08:00 committed by GitHub
commit 07d5886100
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 2109 additions and 1160 deletions

View File

@ -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
View File

@ -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/

View File

@ -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

View File

@ -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" }

View File

@ -1,14 +1,14 @@
error[E0277]: the trait bound `NonClonable: Clone` is not satisfied error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
--> ui_tests/non_clonable.rs:11:23 --> ui_tests/non_clonable.rs:11:23
| |
11 | pub fn test_fn(input: NonClonable) -> bool { 11 | pub fn test_fn(input: NonClonable) -> bool {
| ^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable` | ^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable`
| |
note: required by a bound in `rhai::Dynamic::cast` 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)]

View File

@ -1,14 +1,14 @@
error[E0277]: the trait bound `NonClonable: Clone` is not satisfied error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
--> ui_tests/non_clonable_second.rs:11:27 --> ui_tests/non_clonable_second.rs:11:27
| |
11 | pub fn test_fn(a: u32, b: NonClonable) -> bool { 11 | pub fn test_fn(a: u32, b: NonClonable) -> bool {
| ^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable` | ^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable`
| |
note: required by a bound in `rhai::Dynamic::cast` 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)]

View File

@ -1,17 +1,17 @@
error[E0277]: the trait bound `NonClonable: Clone` is not satisfied error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
--> ui_tests/rhai_fn_non_clonable_return.rs:11:31 --> ui_tests/rhai_fn_non_clonable_return.rs:11:31
| |
10 | #[export_fn] 10 | #[export_fn]
| ------------ in this procedural macro expansion | ------------ in this procedural macro expansion
11 | pub fn test_fn(input: f32) -> NonClonable { 11 | pub fn test_fn(input: f32) -> NonClonable {
| ^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable` | ^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable`
| |
note: required by a bound in `rhai::Dynamic::from` 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)]`
| |
3 | #[derive(Clone)] 3 | #[derive(Clone)]

View File

@ -1,18 +1,18 @@
error[E0277]: the trait bound `NonClonable: Clone` is not satisfied error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
--> ui_tests/rhai_mod_non_clonable_return.rs:12:35 --> ui_tests/rhai_mod_non_clonable_return.rs:12:35
| |
10 | #[export_module] 10 | #[export_module]
| ---------------- in this procedural macro expansion | ---------------- in this procedural macro expansion
11 | pub mod test_mod { 11 | pub mod test_mod {
12 | pub fn test_fn(input: f32) -> NonClonable { 12 | pub fn test_fn(input: f32) -> NonClonable {
| ^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable` | ^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable`
| |
note: required by a bound in `rhai::Dynamic::from` 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)]`
| |
3 | #[derive(Clone)] 3 | #[derive(Clone)]

View File

@ -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`) |

View File

@ -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)
} }

View File

@ -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
} }

View File

@ -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());

View File

@ -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
} }
} }

View File

@ -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(

View File

@ -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
} }

View File

@ -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
} }

View File

@ -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()
#[cfg(not(feature = "no_function"))] | Self::FAST_OPS.bits()
{ | {
Self::ANON_FN #[cfg(not(feature = "no_function"))]
} {
#[cfg(feature = "no_function")] Self::ANON_FN.bits()
{ }
Self::empty() #[cfg(feature = "no_function")]
} {
} Self::empty().bits()
}
},
)
} }
} }

View File

@ -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() {

View File

@ -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)?
}; };

View File

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

View File

@ -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;

View File

@ -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),
) )
} }
} }

View File

@ -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 {

View File

@ -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;

View File

@ -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!");
} }

View File

@ -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,6 +227,49 @@ pub fn make_setter(id: &str) -> Identifier {
} }
impl Engine { impl Engine {
/// An empty raw [`Engine`].
pub const RAW: Self = Self {
global_modules: StaticVec::new_const(),
#[cfg(not(feature = "no_module"))]
global_sub_modules: None,
#[cfg(not(feature = "no_module"))]
module_resolver: None,
interned_strings: None,
disabled_symbols: None,
#[cfg(not(feature = "no_custom_syntax"))]
custom_keywords: None,
#[cfg(not(feature = "no_custom_syntax"))]
custom_syntax: None,
def_var_filter: None,
resolve_var: None,
token_mapper: None,
print: None,
debug: None,
#[cfg(not(feature = "unchecked"))]
progress: None,
options: LangOptions::new(),
def_tag: Dynamic::UNIT,
#[cfg(not(feature = "no_optimize"))]
optimization_level: OptimizationLevel::Simple,
#[cfg(feature = "no_optimize")]
optimization_level: (),
#[cfg(not(feature = "unchecked"))]
limits: crate::api::limits::Limits::new(),
#[cfg(feature = "debugging")]
debugger_interface: None,
};
/// Create a new [`Engine`]. /// Create a new [`Engine`].
#[inline] #[inline]
#[must_use] #[must_use]
@ -235,22 +281,25 @@ impl Engine {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_family = "wasm"))] #[cfg(not(target_family = "wasm"))]
{ {
engine.module_resolver = Box::new(crate::module::resolvers::FileModuleResolver::new()); engine.module_resolver =
Some(Box::new(crate::module::resolvers::FileModuleResolver::new()));
} }
engine.interned_strings = Some(Locked::new(StringsInterner::new()).into());
// default print/debug implementations // default print/debug implementations
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_family = "wasm"))] #[cfg(not(target_family = "wasm"))]
{ {
engine.print = Box::new(|s| println!("{s}")); engine.print = Some(Box::new(|s| println!("{s}")));
engine.debug = Box::new(|s, source, pos| match (source, pos) { engine.debug = Some(Box::new(|s, source, pos| match (source, pos) {
(Some(source), crate::Position::NONE) => println!("{source} | {s}"), (Some(source), crate::Position::NONE) => println!("{source} | {s}"),
#[cfg(not(feature = "no_position"))] #[cfg(not(feature = "no_position"))]
(Some(source), pos) => println!("{source} @ {pos:?} | {s}"), (Some(source), pos) => println!("{source} @ {pos:?} | {s}"),
(None, crate::Position::NONE) => println!("{s}"), (None, crate::Position::NONE) => println!("{s}"),
#[cfg(not(feature = "no_position"))] #[cfg(not(feature = "no_position"))]
(None, pos) => println!("{pos:?} | {s}"), (None, pos) => println!("{pos:?} | {s}"),
}); }));
} }
engine.register_global_module(StandardPackage::new().as_shared_module()); engine.register_global_module(StandardPackage::new().as_shared_module());
@ -259,59 +308,15 @@ impl Engine {
} }
/// Create a new [`Engine`] with minimal built-in functions. /// 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. /// Use [`register_global_module`][Engine::register_global_module] to add packages of functions.
#[inline] #[inline]
#[must_use] #[must_use]
pub fn new_raw() -> Self { pub const fn new_raw() -> Self {
let mut engine = Self { Self::RAW
global_modules: StaticVec::new_const(),
#[cfg(not(feature = "no_module"))]
global_sub_modules: None,
#[cfg(not(feature = "no_module"))]
module_resolver: Box::new(crate::module::resolvers::DummyModuleResolver::new()),
interned_strings: StringsInterner::new().into(),
disabled_symbols: None,
#[cfg(not(feature = "no_custom_syntax"))]
custom_keywords: None,
#[cfg(not(feature = "no_custom_syntax"))]
custom_syntax: None,
def_var_filter: None,
resolve_var: None,
token_mapper: None,
print: Box::new(|_| {}),
debug: Box::new(|_, _, _| {}),
#[cfg(not(feature = "unchecked"))]
progress: None,
options: LangOptions::new(),
def_tag: Dynamic::UNIT,
#[cfg(not(feature = "no_optimize"))]
optimization_level: OptimizationLevel::Simple,
#[cfg(feature = "no_optimize")]
optimization_level: (),
#[cfg(not(feature = "unchecked"))]
limits: crate::api::limits::Limits::new(),
#[cfg(feature = "debugging")]
debugger_interface: None,
};
// Add the global namespace module
let mut global_namespace = Module::new();
global_namespace.flags |= ModuleFlags::INTERNAL;
engine.global_modules.push(global_namespace.into());
engine
} }
/// Get an interned [string][ImmutableString]. /// Get an interned [string][ImmutableString].
@ -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
}
} }

View File

@ -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 {
// Push a new function resolution cache if the stack is empty
if self.0.is_empty() { if self.0.is_empty() {
// Push a new function resolution cache if the stack is empty
self.push_fn_resolution_cache(); self.push_fn_resolution_cache();
} }
self.0.last_mut().unwrap() self.0.last_mut().unwrap()

View File

@ -504,9 +504,7 @@ 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"))]

View File

@ -8,90 +8,91 @@ use std::borrow::Borrow;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
/// Recursively calculate the sizes of an array.
///
/// Sizes returned are `(` [`Array`][crate::Array], [`Map`][crate::Map] and [`String`] `)`.
///
/// # Panics
///
/// Panics if any interior data is shared (should never happen).
#[cfg(not(feature = "no_index"))]
#[inline]
pub fn calc_array_sizes(array: &crate::Array) -> (usize, usize, usize) {
let (mut ax, mut mx, mut sx) = (0, 0, 0);
for value in array {
ax += 1;
match value.0 {
Union::Array(ref a, ..) => {
let (a, m, s) = calc_array_sizes(a);
ax += a;
mx += m;
sx += s;
}
Union::Blob(ref a, ..) => ax += 1 + a.len(),
#[cfg(not(feature = "no_object"))]
Union::Map(ref m, ..) => {
let (a, m, s) = calc_map_sizes(m);
ax += a;
mx += m;
sx += s;
}
Union::Str(ref s, ..) => sx += s.len(),
#[cfg(not(feature = "no_closure"))]
Union::Shared(..) => {
unreachable!("shared values discovered within data")
}
_ => (),
}
}
(ax, mx, sx)
}
/// Recursively calculate the sizes of a map.
///
/// Sizes returned are `(` [`Array`][crate::Array], [`Map`][crate::Map] and [`String`] `)`.
///
/// # Panics
///
/// Panics if any interior data is shared (should never happen).
#[cfg(not(feature = "no_object"))]
#[inline]
pub fn calc_map_sizes(map: &crate::Map) -> (usize, usize, usize) {
let (mut ax, mut mx, mut sx) = (0, 0, 0);
for value in map.values() {
mx += 1;
match value.0 {
#[cfg(not(feature = "no_index"))]
Union::Array(ref a, ..) => {
let (a, m, s) = calc_array_sizes(a);
ax += a;
mx += m;
sx += s;
}
#[cfg(not(feature = "no_index"))]
Union::Blob(ref a, ..) => ax += 1 + a.len(),
Union::Map(ref m, ..) => {
let (a, m, s) = calc_map_sizes(m);
ax += a;
mx += m;
sx += s;
}
Union::Str(ref s, ..) => sx += s.len(),
#[cfg(not(feature = "no_closure"))]
Union::Shared(..) => {
unreachable!("shared values discovered within data")
}
_ => (),
}
}
(ax, mx, sx)
}
impl Dynamic { impl Dynamic {
/// Recursively calculate the sizes of an array.
///
/// Sizes returned are `(` [`Array`][crate::Array], [`Map`][crate::Map] and [`String`] `)`.
///
/// # Panics
///
/// Panics if any interior data is shared (should never happen).
#[cfg(not(feature = "no_index"))]
#[inline]
pub(crate) fn calc_array_sizes(array: &crate::Array) -> (usize, usize, usize) {
let (mut ax, mut mx, mut sx) = (0, 0, 0);
for value in array {
ax += 1;
match value.0 {
Union::Array(ref a, ..) => {
let (a, m, s) = Self::calc_array_sizes(a);
ax += a;
mx += m;
sx += s;
}
Union::Blob(ref a, ..) => ax += 1 + a.len(),
#[cfg(not(feature = "no_object"))]
Union::Map(ref m, ..) => {
let (a, m, s) = Self::calc_map_sizes(m);
ax += a;
mx += m;
sx += s;
}
Union::Str(ref s, ..) => sx += s.len(),
#[cfg(not(feature = "no_closure"))]
Union::Shared(..) => {
unreachable!("shared values discovered within data")
}
_ => (),
}
}
(ax, mx, sx)
}
/// Recursively calculate the sizes of a map.
///
/// Sizes returned are `(` [`Array`][crate::Array], [`Map`][crate::Map] and [`String`] `)`.
///
/// # Panics
///
/// Panics if any interior data is shared (should never happen).
#[cfg(not(feature = "no_object"))]
#[inline]
pub(crate) fn calc_map_sizes(map: &crate::Map) -> (usize, usize, usize) {
let (mut ax, mut mx, mut sx) = (0, 0, 0);
for value in map.values() {
mx += 1;
match value.0 {
#[cfg(not(feature = "no_index"))]
Union::Array(ref a, ..) => {
let (a, m, s) = Self::calc_array_sizes(a);
ax += a;
mx += m;
sx += s;
}
#[cfg(not(feature = "no_index"))]
Union::Blob(ref a, ..) => ax += 1 + a.len(),
Union::Map(ref m, ..) => {
let (a, m, s) = Self::calc_map_sizes(m);
ax += a;
mx += m;
sx += s;
}
Union::Str(ref s, ..) => sx += s.len(),
#[cfg(not(feature = "no_closure"))]
Union::Shared(..) => {
unreachable!("shared values discovered within data")
}
_ => (),
}
}
(ax, mx, sx)
}
/// 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()); self.progress
.as_ref()
.and_then(|progress| {
progress(global.num_operations)
.map(|token| Err(ERR::ErrorTerminated(token, pos).into()))
})
.unwrap_or(Ok(()))
} }
// Report progress
self.progress
.as_ref()
.and_then(|p| p(global.num_operations))
.map_or(Ok(()), |token| Err(ERR::ErrorTerminated(token, pos).into()))
} }
} }

View File

@ -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());

View File

@ -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,29 +239,22 @@ 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.
#[cfg(feature = "debugging")]
let reset =
self.run_debugger_with_reset(global, caches, scope, this_ptr.as_deref_mut(), expr)?;
#[cfg(feature = "debugging")]
auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset));
self.track_operation(global, expr.position())?;
// Function calls should account for a relatively larger portion of expressions because // Function calls should account for a relatively larger portion of expressions because
// binary operators are also function calls. // binary operators are also function calls.
if let Expr::FnCall(x, pos) = expr { if let Expr::FnCall(x, pos) = expr {
#[cfg(feature = "debugging")]
let reset =
self.run_debugger_with_reset(global, caches, scope, this_ptr.as_deref_mut(), expr)?;
#[cfg(feature = "debugging")]
auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset));
self.track_operation(global, expr.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 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(..) => {

View File

@ -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`.

View File

@ -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,

View File

@ -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 args = &mut [&mut *lock_guard, &mut new_val]; let mut done = false;
// Short-circuit built-in op-assignments if under Fast Operators mode
if self.fast_operators() { if self.fast_operators() {
if let Some((func, need_context)) = #[allow(clippy::wildcard_imports)]
get_builtin_op_assignment_fn(op_assign, args[0], args[1]) use Token::*;
{
// Built-in found
auto_restore! { let orig_level = global.level; global.level += 1 }
let context = if need_context { done = true;
let op = op_assign.literal_syntax();
let source = global.source(); // For extremely simple primary data operations, do it directly
Some((self, op, source, &*global, pos).into()) // to avoid the overhead of calling a function.
} else { match (&mut lock_guard.0, &mut new_val.0) {
None (Union::Bool(b1, ..), Union::Bool(b2, ..)) => match op_x {
}; AndAssign => *b1 = *b1 && *b2,
return func(context, args).map(|_| ()); 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;
}
} }
} }
let token = Some(op_assign); if !done {
let op_assign = op_assign.literal_syntax(); let opx = Some(op_x);
let args = &mut [&mut *lock_guard, &mut new_val];
match self.exec_native_fn_call(global, caches, op_assign, token, hash1, args, true, pos) match self
{ .exec_native_fn_call(global, caches, op_x_str, opx, hash_x, args, true, pos)
Ok(_) => (),
Err(err) if matches!(*err, ERR::ErrorFunctionNotFound(ref f, ..) if f.starts_with(op_assign)) =>
{ {
// Expand to `var = var op rhs` Ok(_) => (),
let token = Some(op); Err(err) if matches!(*err, ERR::ErrorFunctionNotFound(ref f, ..) if f.starts_with(op_x_str)) =>
let op = op.literal_syntax(); {
// Expand to `var = var op rhs`
let op = Some(op);
*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,9 +452,12 @@ 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, ..) => {
self.eval_stmt_block(global, caches, scope, this_ptr, statements, true) if statements.is_empty() {
Ok(Dynamic::UNIT)
} else {
self.eval_stmt_block(global, caches, scope, this_ptr, statements, true)
}
} }
// If statement // If statement
@ -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 {
self.eval_stmt_block(global, caches, scope, this_ptr, if_block, true) true if !if_block.is_empty() => {
} else if !guard_val && !else_block.is_empty() { self.eval_stmt_block(global, caches, scope, this_ptr, if_block, true)
self.eval_stmt_block(global, caches, scope, this_ptr, else_block, true) }
} else { false if !else_block.is_empty() => {
Ok(Dynamic::UNIT) self.eval_stmt_block(global, caches, scope, this_ptr, else_block, true)
}
_ => 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(
Err(err) if matches!(*err, ERR::ErrorModuleNotFound(..)) => None, |r| match r.resolve_raw(self, global, scope, &path, path_pos) {
result => Some(result), Err(err) if matches!(*err, ERR::ErrorModuleNotFound(..)) => None,
}) 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(|| {

View File

@ -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,
}; };
} }

View File

@ -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 => {
let text = result.into_immutable_string().map_err(|typ| { if let Some(ref print) = self.print {
let t = self.map_type_name(type_name::<ImmutableString>()).into(); let text = result.into_immutable_string().map_err(|typ| {
ERR::ErrorMismatchOutputType(t, typ.into(), pos) let t = self.map_type_name(type_name::<ImmutableString>()).into();
})?; ERR::ErrorMismatchOutputType(t, typ.into(), pos)
((*self.print)(&text).into(), false) })?;
(print(&text).into(), false)
} else {
(Dynamic::UNIT, false)
}
} }
KEYWORD_DEBUG => { KEYWORD_DEBUG => {
let text = result.into_immutable_string().map_err(|typ| { if let Some(ref debug) = self.debug {
let t = self.map_type_name(type_name::<ImmutableString>()).into(); let text = result.into_immutable_string().map_err(|typ| {
ERR::ErrorMismatchOutputType(t, typ.into(), pos) let t = self.map_type_name(type_name::<ImmutableString>()).into();
})?; 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 {
let operand = &mut [&mut value]; Union::Bool(b, ..) => Ok((!b).into()),
self.exec_fn_call( _ => {
global, caches, None, name, op_token, *hashes, operand, false, false, pos, let operand = &mut [&mut value];
) self.exec_fn_call(
.map(|(v, ..)| v) global, caches, None, name, op_token, *hashes, operand, false, false, pos,
}); )
.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);
} }

View File

@ -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
} }

View File

@ -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() {

View File

@ -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;
} }

View File

@ -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,14 +314,9 @@ 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)
.map_err(|err| Box::new(ERR::ErrorInModule(path.to_string(), err, pos)))?
let m: Shared<_> = match global { .into();
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)))?
.into();
if self.is_cache_enabled() { if self.is_cache_enabled() {
locked_write(&self.cache).insert(file_path, m.clone()); locked_write(&self.cache).insert(file_path, m.clone());
@ -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.

View File

@ -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> {

View File

@ -73,11 +73,7 @@ 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() { self.0.contains_key(path)
false
} else {
self.0.contains_key(path)
}
} }
/// Get an iterator of all the [modules][Module]. /// Get an iterator of all the [modules][Module].
#[inline] #[inline]
@ -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
} }
} }

View File

@ -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);

View File

@ -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()

View File

@ -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);
} }
} }

View File

@ -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 // Other range
if !has_condition && ranges.is_empty() && r.len() <= SMALL_SWITCH_RANGE r.set_index(index);
{ ranges.push(r);
// 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
r.set_index(index);
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) => {
self.parse_do(input, state, lib, settings.level_up()?)? Expr::Stmt(Box::new(
.into(), self.parse_do(input, state, lib, settings.level_up()?)?
)), .into(),
Token::For if settings.has_option(LangOptions::LOOP_EXPR) => Expr::Stmt(Box::new( ))
self.parse_for(input, state, lib, settings.level_up()?)? }
.into(), 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()?)?
.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))
} }

View File

@ -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!(
size_of::<module::FuncInfo>(),
if IS_32_BIT { 16 } else { 32 }
);
} }
#[cfg(target_pointer_width = "64")] // The following only on 64-bit platforms
{
assert_eq!(size_of::<Scope>(), 536); if !cfg!(target_pointer_width = "64") {
assert_eq!( return;
size_of::<FnPtr>(),
if cfg!(feature = "no_function") {
72
} else {
80
}
);
assert_eq!(size_of::<LexError>(), 56);
assert_eq!(
size_of::<ParseError>(),
if cfg!(feature = "no_position") { 8 } else { 16 }
);
assert_eq!(size_of::<EvalAltResult>(), 64);
assert_eq!(
size_of::<NativeCallContext>(),
if cfg!(feature = "no_position") {
48
} else {
56
}
);
} }
assert_eq!(size_of::<Scope>(), 536);
assert_eq!(
size_of::<FnPtr>(),
80 - if cfg!(feature = "no_function") {
WORD_SIZE
} else {
0
}
);
assert_eq!(size_of::<LexError>(), 56);
assert_eq!(
size_of::<ParseError>(),
16 - if cfg!(feature = "no_position") {
WORD_SIZE
} else {
0
}
);
assert_eq!(size_of::<EvalAltResult>(), 64);
assert_eq!(
size_of::<NativeCallContext>(),
56 - if cfg!(feature = "no_position") {
WORD_SIZE
} else {
0
}
);
} }

View File

@ -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);
#[cfg(not(feature = "unicode-xid-ident"))]
return x.is_ascii_alphanumeric() || x == '_';
} }
/// Is a character valid to start 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_first_alphabetic(x: char) -> bool { pub fn is_reserved_keyword_or_symbol(syntax: &str) -> (bool, bool, bool) {
x.is_ascii_alphabetic() // 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;
/// Is a character valid for an identifier? if !(MIN_RESERVED_LEN..=MAX_RESERVED_LEN).contains(&len) {
#[cfg(not(feature = "unicode-xid-ident"))] return (false, false, false);
#[inline(always)] }
#[must_use]
pub const fn is_id_continue(x: char) -> bool {
x.is_ascii_alphanumeric() || x == '_'
}
/// 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
View 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
View 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
View 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

View File

@ -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(

View File

@ -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"),

View File

@ -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

View File

@ -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")?;

View File

@ -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(())
} }

View File

@ -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(())
} }

View File

@ -21,7 +21,7 @@ fn test_comments() -> Result<(), Box<EvalAltResult>> {
42 42
); );
engine.run("/* Hello world */").unwrap(); engine.run("/* Hello world */")?;
Ok(()) Ok(())
} }

View File

@ -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(())
} }

View File

@ -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();

View File

@ -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,
} }
" "
) )

View File

@ -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(())

View File

@ -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(())

View File

@ -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(())
}

View File

@ -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"),

View File

@ -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(())
} }

View File

@ -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(())
}

View File

@ -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(())

View File

@ -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,

View File

@ -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(())
} }