Merge pull request #708 from schungx/master
Speed improvement attempts.
This commit is contained in:
commit
07d5886100
44
.github/workflows/build.yml
vendored
44
.github/workflows/build.yml
vendored
@ -43,28 +43,28 @@ jobs:
|
||||
os: [ubuntu-latest]
|
||||
flags:
|
||||
- ""
|
||||
- "--features debugging"
|
||||
- "--features metadata,serde,internals"
|
||||
- "--features unchecked,serde,metadata,internals,debugging"
|
||||
- "--features sync,serde,metadata,internals,debugging"
|
||||
- "--features no_position,serde,metadata,internals,debugging"
|
||||
- "--features no_optimize,serde,metadata,internals,debugging"
|
||||
- "--features no_float,serde,metadata,internals,debugging"
|
||||
- "--features f32_float,serde,metadata,internals,debugging"
|
||||
- "--features decimal,serde,metadata,internals,debugging"
|
||||
- "--features no_custom_syntax,serde,metadata,internals,debugging"
|
||||
- "--features no_float,decimal"
|
||||
- "--tests --features only_i32,serde,metadata,internals,debugging"
|
||||
- "--features only_i64,serde,metadata,internals,debugging"
|
||||
- "--features no_index,serde,metadata,internals,debugging"
|
||||
- "--features no_object,serde,metadata,internals,debugging"
|
||||
- "--features no_function,serde,metadata,internals,debugging"
|
||||
- "--features no_module,serde,metadata,internals,debugging"
|
||||
- "--features no_time,serde,metadata,internals,debugging"
|
||||
- "--features no_closure,serde,metadata,internals,debugging"
|
||||
- "--features 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 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,debugging"
|
||||
- "--features testing-environ,metadata,serde,internals"
|
||||
- "--features testing-environ,unchecked,serde,metadata,internals,debugging"
|
||||
- "--features testing-environ,sync,serde,metadata,internals,debugging"
|
||||
- "--features testing-environ,no_position,serde,metadata,internals,debugging"
|
||||
- "--features testing-environ,no_optimize,serde,metadata,internals,debugging"
|
||||
- "--features testing-environ,no_float,serde,metadata,internals,debugging"
|
||||
- "--features testing-environ,f32_float,serde,metadata,internals,debugging"
|
||||
- "--features testing-environ,decimal,serde,metadata,internals,debugging"
|
||||
- "--features testing-environ,no_custom_syntax,serde,metadata,internals,debugging"
|
||||
- "--features testing-environ,no_float,decimal"
|
||||
- "--tests --features testing-environ,only_i32,serde,metadata,internals,debugging"
|
||||
- "--features testing-environ,only_i64,serde,metadata,internals,debugging"
|
||||
- "--features testing-environ,no_index,serde,metadata,internals,debugging"
|
||||
- "--features testing-environ,no_object,serde,metadata,internals,debugging"
|
||||
- "--features testing-environ,no_function,serde,metadata,internals,debugging"
|
||||
- "--features testing-environ,no_module,serde,metadata,internals,debugging"
|
||||
- "--features testing-environ,no_time,serde,metadata,internals,debugging"
|
||||
- "--features testing-environ,no_closure,serde,metadata,internals,debugging"
|
||||
- "--features testing-environ,unicode-xid-ident,serde,metadata,internals,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 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]
|
||||
experimental: [false]
|
||||
include:
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,6 +6,7 @@ benches/results
|
||||
clippy.toml
|
||||
Rhai.toml
|
||||
**/*.bat
|
||||
**/*.exe
|
||||
doc/rhai-sync.json
|
||||
doc/rhai.json
|
||||
.idea/
|
||||
|
15
CHANGELOG.md
15
CHANGELOG.md
@ -12,11 +12,26 @@ Bug fixes
|
||||
* Syntax such as `foo.bar::baz` no longer panics, but returns a proper parse error.
|
||||
* `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`.
|
||||
* 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
|
||||
------------
|
||||
|
||||
* `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.
|
||||
* 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
|
||||
|
@ -34,7 +34,7 @@ serde_json = { version = "1.0", default-features = false, features = ["alloc"],
|
||||
unicode-xid = { version = "0.2", default-features = false, optional = true }
|
||||
rust_decimal = { version = "1.16", default-features = false, features = ["maths"], 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 }
|
||||
|
||||
[dev-dependencies]
|
||||
@ -117,6 +117,11 @@ std = ["ahash/std", "num-traits/std", "smartstring/std"]
|
||||
## Use [`stdweb`](https://crates.io/crates/stdweb) as JavaScript interface.
|
||||
stdweb = ["getrandom/js", "instant/stdweb"]
|
||||
|
||||
#! ### Features used in testing environments only
|
||||
|
||||
## Running under a testing environment.
|
||||
testing-environ = []
|
||||
|
||||
[[bin]]
|
||||
name = "rhai-repl"
|
||||
required-features = ["rustyline"]
|
||||
@ -151,4 +156,4 @@ features = ["document-features", "metadata", "serde", "internals", "decimal", "d
|
||||
[patch.crates-io]
|
||||
# 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.
|
||||
rustyline = { git = "https://github.com/schungx/rustyline", branch = "v10" }
|
||||
rustyline = { git = "https://github.com/schungx/rustyline", branch = "v11" }
|
||||
|
@ -8,7 +8,7 @@ note: required by a bound in `rhai::Dynamic::cast`
|
||||
--> $WORKSPACE/src/types/dynamic.rs
|
||||
|
|
||||
| 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)]`
|
||||
|
|
||||
3 | #[derive(Clone)]
|
||||
|
@ -8,7 +8,7 @@ note: required by a bound in `rhai::Dynamic::cast`
|
||||
--> $WORKSPACE/src/types/dynamic.rs
|
||||
|
|
||||
| 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)]`
|
||||
|
|
||||
3 | #[derive(Clone)]
|
||||
|
@ -10,7 +10,7 @@ note: required by a bound in `rhai::Dynamic::from`
|
||||
--> $WORKSPACE/src/types/dynamic.rs
|
||||
|
|
||||
| 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)
|
||||
help: consider annotating `NonClonable` with `#[derive(Clone)]`
|
||||
|
|
||||
|
@ -11,7 +11,7 @@ note: required by a bound in `rhai::Dynamic::from`
|
||||
--> $WORKSPACE/src/types/dynamic.rs
|
||||
|
|
||||
| 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)
|
||||
help: consider annotating `NonClonable` with `#[derive(Clone)]`
|
||||
|
|
||||
|
@ -28,4 +28,5 @@ Sub-Directories
|
||||
| `func` | Support for function calls |
|
||||
| `eval` | Evaluation engine |
|
||||
| `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`) |
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
use crate::func::native::locked_write;
|
||||
use crate::parser::{ParseResult, ParseState};
|
||||
use crate::types::StringsInterner;
|
||||
use crate::{Engine, OptimizationLevel, Scope, AST};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
@ -126,7 +127,7 @@ impl Engine {
|
||||
let path = path.clone();
|
||||
|
||||
match self
|
||||
.module_resolver
|
||||
.module_resolver()
|
||||
.resolve_ast(self, None, &path, crate::Position::NONE)
|
||||
{
|
||||
Some(Ok(module_ast)) => collect_imports(&module_ast, &resolver, &mut imports),
|
||||
@ -135,7 +136,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
let module =
|
||||
self.module_resolver
|
||||
self.module_resolver()
|
||||
.resolve(self, None, &path, crate::Position::NONE)?;
|
||||
|
||||
let module = shared_take_or_clone(module);
|
||||
@ -223,7 +224,17 @@ impl Engine {
|
||||
optimization_level: OptimizationLevel,
|
||||
) -> ParseResult<AST> {
|
||||
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 mut _ast = self.parse(stream.peekable(), state, optimization_level)?;
|
||||
#[cfg(feature = "metadata")]
|
||||
@ -294,7 +305,17 @@ impl Engine {
|
||||
) -> ParseResult<AST> {
|
||||
let scripts = [script];
|
||||
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);
|
||||
self.parse_global_expr(stream.peekable(), state, |_| {}, self.optimization_level)
|
||||
}
|
||||
|
@ -232,7 +232,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
let token = Token::lookup_symbol_from_syntax(s).or_else(|| {
|
||||
if is_reserved_keyword_or_symbol(s) {
|
||||
if is_reserved_keyword_or_symbol(s).0 {
|
||||
Some(Token::Reserved(Box::new(s.into())))
|
||||
} else {
|
||||
None
|
||||
@ -255,7 +255,8 @@ impl Engine {
|
||||
// Markers not in first position
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
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() => {
|
||||
// Make it a custom keyword/symbol if it is disabled or reserved
|
||||
if (self
|
||||
@ -274,6 +275,7 @@ impl Engine {
|
||||
}
|
||||
s.into()
|
||||
}
|
||||
|
||||
// Standard keyword in first position but not disabled
|
||||
_ if segments.is_empty()
|
||||
&& token.as_ref().map_or(false, Token::is_standard_keyword)
|
||||
@ -291,8 +293,11 @@ impl Engine {
|
||||
)
|
||||
.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
|
||||
if self
|
||||
.disabled_symbols
|
||||
@ -310,6 +315,7 @@ impl Engine {
|
||||
}
|
||||
s.into()
|
||||
}
|
||||
|
||||
// Anything else is an error
|
||||
_ => {
|
||||
return Err(LexError::ImproperSymbol(
|
||||
@ -326,23 +332,20 @@ impl Engine {
|
||||
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() {
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
// The first keyword is the discriminator
|
||||
// The first keyword/symbol is the discriminator
|
||||
let key = segments[0].clone();
|
||||
|
||||
self.register_custom_syntax_with_state_raw(
|
||||
key,
|
||||
// Construct the parsing function
|
||||
move |stream, _, _| {
|
||||
if stream.len() >= segments.len() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(segments[stream.len()].clone()))
|
||||
}
|
||||
move |stream, _, _| match stream.len() {
|
||||
len if len >= segments.len() => Ok(None),
|
||||
len => Ok(Some(segments[len].clone())),
|
||||
},
|
||||
scope_may_be_changed,
|
||||
move |context, expressions, _| func(context, expressions),
|
||||
@ -399,7 +402,8 @@ impl Engine {
|
||||
parse: Box::new(parse),
|
||||
func: Box::new(func),
|
||||
scope_may_be_changed,
|
||||
},
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
self
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ use crate::eval::{Caches, GlobalRuntimeState};
|
||||
use crate::func::native::locked_write;
|
||||
use crate::parser::ParseState;
|
||||
use crate::types::dynamic::Variant;
|
||||
use crate::types::StringsInterner;
|
||||
use crate::{
|
||||
Dynamic, Engine, OptimizationLevel, Position, RhaiResult, RhaiResultOf, Scope, AST, ERR,
|
||||
};
|
||||
@ -119,7 +120,15 @@ impl Engine {
|
||||
) -> RhaiResultOf<T> {
|
||||
let scripts = [script];
|
||||
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());
|
||||
|
||||
|
@ -286,7 +286,7 @@ impl Engine {
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
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
|
||||
}
|
||||
/// Override default action of `debug` (print to stdout using [`println!`])
|
||||
@ -336,7 +336,7 @@ impl Engine {
|
||||
&mut self,
|
||||
callback: impl Fn(&str, Option<&str>, Position) + SendSync + 'static,
|
||||
) -> &mut Self {
|
||||
self.debug = Box::new(callback);
|
||||
self.debug = Some(Box::new(callback));
|
||||
self
|
||||
}
|
||||
/// _(debugging)_ Register a callback for debugging.
|
||||
@ -363,7 +363,7 @@ impl Engine {
|
||||
+ SendSync
|
||||
+ 'static,
|
||||
) -> &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
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
use crate::func::native::locked_write;
|
||||
use crate::parser::{ParseSettingFlags, ParseState};
|
||||
use crate::tokenizer::Token;
|
||||
use crate::types::StringsInterner;
|
||||
use crate::{Engine, LexError, Map, OptimizationLevel, RhaiResultOf};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
@ -115,7 +116,16 @@ impl Engine {
|
||||
);
|
||||
|
||||
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);
|
||||
|
||||
self.parse_global_expr(
|
||||
|
@ -54,7 +54,10 @@ impl Engine {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
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`].
|
||||
@ -66,7 +69,7 @@ impl Engine {
|
||||
&mut self,
|
||||
resolver: impl crate::ModuleResolver + 'static,
|
||||
) -> &mut Self {
|
||||
self.module_resolver = Box::new(resolver);
|
||||
self.module_resolver = Some(Box::new(resolver));
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,11 @@ impl Engine {
|
||||
);
|
||||
|
||||
#[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
|
||||
}
|
||||
|
@ -38,23 +38,26 @@ impl LangOptions {
|
||||
/// Create a new [`LangOptions`] with default values.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::IF_EXPR
|
||||
| Self::SWITCH_EXPR
|
||||
| Self::STMT_EXPR
|
||||
| Self::LOOPING
|
||||
| Self::SHADOWING
|
||||
| Self::FAST_OPS
|
||||
pub const fn new() -> Self {
|
||||
Self::from_bits_truncate(
|
||||
Self::IF_EXPR.bits()
|
||||
| Self::SWITCH_EXPR.bits()
|
||||
| Self::LOOP_EXPR.bits()
|
||||
| Self::STMT_EXPR.bits()
|
||||
| Self::LOOPING.bits()
|
||||
| Self::SHADOWING.bits()
|
||||
| Self::FAST_OPS.bits()
|
||||
| {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
{
|
||||
Self::ANON_FN
|
||||
Self::ANON_FN.bits()
|
||||
}
|
||||
#[cfg(feature = "no_function")]
|
||||
{
|
||||
Self::empty()
|
||||
}
|
||||
Self::empty().bits()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
//! Module that defines the public function/module registration API of [`Engine`].
|
||||
|
||||
use crate::func::{FnCallArgs, RegisterNativeFunction, SendSync};
|
||||
use crate::module::ModuleFlags;
|
||||
use crate::types::dynamic::Variant;
|
||||
use crate::{
|
||||
Engine, FnAccess, FnNamespace, Identifier, Module, NativeCallContext, RhaiResultOf, Shared,
|
||||
@ -14,20 +15,18 @@ use std::prelude::v1::*;
|
||||
use crate::func::register::Mut;
|
||||
|
||||
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
|
||||
/// (which is the first module in `global_modules`).
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub(crate) fn global_namespace_mut(&mut self) -> &mut Module {
|
||||
let module = self.global_modules.first_mut().unwrap();
|
||||
Shared::get_mut(module).expect("not shared")
|
||||
fn global_namespace_mut(&mut self) -> &mut Module {
|
||||
if self.global_modules.is_empty() {
|
||||
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`].
|
||||
///
|
||||
@ -677,6 +676,9 @@ impl Engine {
|
||||
/// modules are searched in reverse order.
|
||||
#[inline(always)]
|
||||
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.
|
||||
// The first module is always the global namespace.
|
||||
self.global_modules.insert(1, module);
|
||||
@ -729,7 +731,7 @@ impl Engine {
|
||||
name: &str,
|
||||
module: SharedModule,
|
||||
) {
|
||||
let separator = crate::tokenizer::Token::DoubleColon.literal_syntax();
|
||||
let separator = crate::engine::NAMESPACE_SEPARATOR;
|
||||
|
||||
if name.contains(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> {
|
||||
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"))]
|
||||
for (name, m) in self.global_sub_modules.as_deref().into_iter().flatten() {
|
||||
|
@ -3,6 +3,7 @@
|
||||
use crate::eval::{Caches, GlobalRuntimeState};
|
||||
use crate::func::native::locked_write;
|
||||
use crate::parser::ParseState;
|
||||
use crate::types::StringsInterner;
|
||||
use crate::{Engine, RhaiResultOf, Scope, AST};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
@ -59,7 +60,17 @@ impl Engine {
|
||||
let scripts = [script];
|
||||
let ast = {
|
||||
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);
|
||||
self.parse(stream.peekable(), state, self.optimization_level)?
|
||||
};
|
||||
|
106
src/ast/ast.rs
106
src/ast/ast.rs
@ -23,9 +23,9 @@ pub struct AST {
|
||||
source: Option<ImmutableString>,
|
||||
/// [`AST`] documentation.
|
||||
#[cfg(feature = "metadata")]
|
||||
doc: crate::SmartString,
|
||||
doc: Option<Box<crate::SmartString>>,
|
||||
/// Global statements.
|
||||
body: StmtBlock,
|
||||
body: Option<Box<StmtBlock>>,
|
||||
/// Script-defined functions.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
lib: crate::SharedModule,
|
||||
@ -54,7 +54,14 @@ impl fmt::Debug for AST {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
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"))]
|
||||
for (.., fn_def) in self.lib.iter_script_fn() {
|
||||
@ -75,11 +82,13 @@ impl AST {
|
||||
statements: impl IntoIterator<Item = Stmt>,
|
||||
#[cfg(not(feature = "no_function"))] functions: impl Into<crate::SharedModule>,
|
||||
) -> Self {
|
||||
let stmt = StmtBlock::new(statements, Position::NONE, Position::NONE);
|
||||
|
||||
Self {
|
||||
source: None,
|
||||
#[cfg(feature = "metadata")]
|
||||
doc: crate::SmartString::new_const(),
|
||||
body: StmtBlock::new(statements, Position::NONE, Position::NONE),
|
||||
doc: None,
|
||||
body: (!stmt.is_empty()).then(|| stmt.into()),
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
lib: functions.into(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
@ -95,11 +104,13 @@ impl AST {
|
||||
statements: impl IntoIterator<Item = Stmt>,
|
||||
#[cfg(not(feature = "no_function"))] functions: impl Into<crate::SharedModule>,
|
||||
) -> Self {
|
||||
let stmt = StmtBlock::new(statements, Position::NONE, Position::NONE);
|
||||
|
||||
Self {
|
||||
source: None,
|
||||
#[cfg(feature = "metadata")]
|
||||
doc: crate::SmartString::new_const(),
|
||||
body: StmtBlock::new(statements, Position::NONE, Position::NONE),
|
||||
doc: None,
|
||||
body: (!stmt.is_empty()).then(|| stmt.into()),
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
lib: functions.into(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
@ -148,8 +159,8 @@ impl AST {
|
||||
Self {
|
||||
source: None,
|
||||
#[cfg(feature = "metadata")]
|
||||
doc: crate::SmartString::new_const(),
|
||||
body: StmtBlock::NONE,
|
||||
doc: None,
|
||||
body: None,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
lib: crate::Module::new().into(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
@ -178,11 +189,7 @@ impl AST {
|
||||
.as_mut()
|
||||
.map(|m| m.set_id(source.clone()));
|
||||
|
||||
if source.is_empty() {
|
||||
self.source = None;
|
||||
} else {
|
||||
self.source = Some(source);
|
||||
}
|
||||
self.source = (!source.is_empty()).then(|| source);
|
||||
|
||||
self
|
||||
}
|
||||
@ -202,14 +209,14 @@ impl AST {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn doc(&self) -> &str {
|
||||
&self.doc
|
||||
self.doc.as_ref().map(|s| s.as_str()).unwrap_or_default()
|
||||
}
|
||||
/// Clear the documentation.
|
||||
/// Exported under the `metadata` feature only.
|
||||
#[cfg(feature = "metadata")]
|
||||
#[inline(always)]
|
||||
pub fn clear_doc(&mut self) -> &mut Self {
|
||||
self.doc.clear();
|
||||
self.doc = None;
|
||||
self
|
||||
}
|
||||
/// Get a mutable reference to the documentation.
|
||||
@ -219,8 +226,8 @@ impl AST {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn doc_mut(&mut self) -> &mut crate::SmartString {
|
||||
&mut self.doc
|
||||
pub(crate) fn doc_mut(&mut self) -> Option<&mut crate::SmartString> {
|
||||
self.doc.as_deref_mut()
|
||||
}
|
||||
/// Set the documentation.
|
||||
///
|
||||
@ -228,14 +235,18 @@ impl AST {
|
||||
#[cfg(feature = "metadata")]
|
||||
#[inline(always)]
|
||||
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.
|
||||
#[cfg(not(feature = "internals"))]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub(crate) fn statements(&self) -> &[Stmt] {
|
||||
self.body.statements()
|
||||
self.body
|
||||
.as_deref()
|
||||
.map(StmtBlock::statements)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
/// _(internals)_ Get the statements.
|
||||
/// Exported under the `internals` feature only.
|
||||
@ -243,14 +254,20 @@ impl AST {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn statements(&self) -> &[Stmt] {
|
||||
self.body.statements()
|
||||
self.body
|
||||
.as_deref()
|
||||
.map(StmtBlock::statements)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
/// Extract the statements.
|
||||
#[allow(dead_code)]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
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?
|
||||
///
|
||||
@ -344,7 +361,7 @@ impl AST {
|
||||
source: self.source.clone(),
|
||||
#[cfg(feature = "metadata")]
|
||||
doc: self.doc.clone(),
|
||||
body: StmtBlock::NONE,
|
||||
body: None,
|
||||
lib: lib.into(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
resolver: self.resolver.clone(),
|
||||
@ -542,15 +559,15 @@ impl AST {
|
||||
other: &Self,
|
||||
_filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
|
||||
) -> Self {
|
||||
let merged = match (self.body.is_empty(), other.body.is_empty()) {
|
||||
(false, false) => {
|
||||
let mut body = self.body.clone();
|
||||
body.extend(other.body.iter().cloned());
|
||||
let merged = match (&self.body, &other.body) {
|
||||
(Some(body), Some(other)) => {
|
||||
let mut body = body.as_ref().clone();
|
||||
body.extend(other.iter().cloned());
|
||||
body
|
||||
}
|
||||
(false, true) => self.body.clone(),
|
||||
(true, false) => other.body.clone(),
|
||||
(true, true) => StmtBlock::NONE,
|
||||
(Some(body), None) => body.as_ref().clone(),
|
||||
(None, Some(body)) => body.as_ref().clone(),
|
||||
(None, None) => StmtBlock::NONE,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
@ -598,11 +615,13 @@ impl AST {
|
||||
}
|
||||
|
||||
#[cfg(feature = "metadata")]
|
||||
if !other.doc.is_empty() {
|
||||
if !_ast.doc.is_empty() {
|
||||
_ast.doc.push('\n');
|
||||
if let Some(ref other_doc) = other.doc {
|
||||
if let Some(ref mut ast_doc) = _ast.doc {
|
||||
ast_doc.push('\n');
|
||||
ast_doc.push_str(other_doc);
|
||||
} else {
|
||||
_ast.doc = Some(other_doc.clone());
|
||||
}
|
||||
_ast.doc.push_str(other.doc());
|
||||
}
|
||||
|
||||
_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"))]
|
||||
if !other.lib.is_empty() {
|
||||
@ -698,11 +722,13 @@ impl AST {
|
||||
}
|
||||
|
||||
#[cfg(feature = "metadata")]
|
||||
if !other.doc.is_empty() {
|
||||
if !self.doc.is_empty() {
|
||||
self.doc.push('\n');
|
||||
if let Some(other_doc) = other.doc {
|
||||
if let Some(ref mut self_doc) = self.doc {
|
||||
self_doc.push('\n');
|
||||
self_doc.push_str(&other_doc);
|
||||
} else {
|
||||
self.doc = Some(other_doc);
|
||||
}
|
||||
self.doc.push_str(&other.doc);
|
||||
}
|
||||
|
||||
self
|
||||
@ -785,7 +811,7 @@ impl AST {
|
||||
/// Clear all statements in the [`AST`], leaving only function definitions.
|
||||
#[inline(always)]
|
||||
pub fn clear_statements(&mut self) -> &mut Self {
|
||||
self.body = StmtBlock::NONE;
|
||||
self.body = None;
|
||||
self
|
||||
}
|
||||
/// Extract all top-level literal constant and/or variable definitions.
|
||||
|
@ -185,7 +185,7 @@ impl FnCallHashes {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn script(&self) -> u64 {
|
||||
assert!(self.script.is_some());
|
||||
debug_assert!(self.script.is_some());
|
||||
self.script.unwrap()
|
||||
}
|
||||
}
|
||||
@ -248,11 +248,7 @@ impl FnCallExpr {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn constant_args(&self) -> bool {
|
||||
if self.args.is_empty() {
|
||||
true
|
||||
} else {
|
||||
self.args.iter().all(Expr::is_constant)
|
||||
}
|
||||
self.args.is_empty() || self.args.iter().all(Expr::is_constant)
|
||||
}
|
||||
}
|
||||
|
||||
@ -383,7 +379,7 @@ impl fmt::Debug for Expr {
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
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();
|
||||
if !pos.is_none() {
|
||||
display_pos = pos;
|
||||
|
@ -2,7 +2,6 @@
|
||||
#![cfg(not(feature = "no_module"))]
|
||||
|
||||
use crate::ast::Ident;
|
||||
use crate::tokenizer::Token;
|
||||
use crate::{Position, StaticVec};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
@ -46,7 +45,7 @@ impl fmt::Debug for Namespace {
|
||||
.iter()
|
||||
.map(Ident::as_str)
|
||||
.collect::<StaticVec<_>>()
|
||||
.join(Token::DoubleColon.literal_syntax()),
|
||||
.join(crate::engine::NAMESPACE_SEPARATOR),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -63,7 +62,7 @@ impl fmt::Display for Namespace {
|
||||
.iter()
|
||||
.map(Ident::as_str)
|
||||
.collect::<StaticVec<_>>()
|
||||
.join(Token::DoubleColon.literal_syntax()),
|
||||
.join(crate::engine::NAMESPACE_SEPARATOR),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ pub struct ScriptFnDef {
|
||||
///
|
||||
/// Each line in non-block doc-comments starts with `///`.
|
||||
#[cfg(feature = "metadata")]
|
||||
pub comments: Box<[crate::Identifier]>,
|
||||
pub comments: Box<[crate::SmartString]>,
|
||||
}
|
||||
|
||||
impl fmt::Display for ScriptFnDef {
|
||||
|
113
src/ast/stmt.rs
113
src/ast/stmt.rs
@ -1,17 +1,18 @@
|
||||
//! Module defining script statements.
|
||||
|
||||
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::types::dynamic::Union;
|
||||
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")]
|
||||
use std::prelude::v1::*;
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
collections::BTreeMap,
|
||||
fmt,
|
||||
hash::Hash,
|
||||
hash::{Hash, Hasher},
|
||||
mem,
|
||||
num::NonZeroUsize,
|
||||
ops::{Deref, DerefMut, Range, RangeInclusive},
|
||||
@ -29,8 +30,12 @@ pub struct OpAssignment {
|
||||
hash_op: u64,
|
||||
/// Op-assignment operator.
|
||||
op_assign: Token,
|
||||
/// Syntax of op-assignment operator.
|
||||
op_assign_syntax: &'static str,
|
||||
/// Underlying operator.
|
||||
op: Token,
|
||||
/// Syntax of underlying operator.
|
||||
op_syntax: &'static str,
|
||||
/// [Position] of the op-assignment operator.
|
||||
pos: Position,
|
||||
}
|
||||
@ -44,7 +49,9 @@ impl OpAssignment {
|
||||
hash_op_assign: 0,
|
||||
hash_op: 0,
|
||||
op_assign: Token::Equals,
|
||||
op_assign_syntax: OP_EQUALS,
|
||||
op: Token::Equals,
|
||||
op_syntax: OP_EQUALS,
|
||||
pos,
|
||||
}
|
||||
}
|
||||
@ -56,17 +63,28 @@ impl OpAssignment {
|
||||
}
|
||||
/// 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`: Hash of the underlying operator call (for fallback).
|
||||
/// * `op_assign`: Op-assignment operator.
|
||||
/// * `op_assign_syntax`: Syntax of op-assignment operator.
|
||||
/// * `op`: Underlying operator.
|
||||
/// * `op_syntax`: Syntax of underlying operator.
|
||||
#[must_use]
|
||||
#[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() {
|
||||
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 {
|
||||
None
|
||||
}
|
||||
@ -99,11 +117,16 @@ impl OpAssignment {
|
||||
.get_base_op_from_assignment()
|
||||
.expect("op-assignment operator");
|
||||
|
||||
let op_assign_syntax = op_assign.literal_syntax();
|
||||
let op_syntax = op.literal_syntax();
|
||||
|
||||
Self {
|
||||
hash_op_assign: calc_fn_hash(None, op_assign.literal_syntax(), 2),
|
||||
hash_op: calc_fn_hash(None, op.literal_syntax(), 2),
|
||||
hash_op_assign: calc_fn_hash(None, op_assign_syntax, 2),
|
||||
hash_op: calc_fn_hash(None, op_syntax, 2),
|
||||
op_assign,
|
||||
op_assign_syntax,
|
||||
op,
|
||||
op_syntax,
|
||||
pos,
|
||||
}
|
||||
}
|
||||
@ -139,7 +162,9 @@ impl fmt::Debug for OpAssignment {
|
||||
.field("hash_op_assign", &self.hash_op_assign)
|
||||
.field("hash_op", &self.hash_op)
|
||||
.field("op_assign", &self.op_assign)
|
||||
.field("op_assign_syntax", &self.op_assign_syntax)
|
||||
.field("op", &self.op)
|
||||
.field("op_syntax", &self.op_syntax)
|
||||
.field("pos", &self.pos)
|
||||
.finish()
|
||||
} else {
|
||||
@ -233,7 +258,7 @@ impl IntoIterator for RangeCase {
|
||||
type Item = INT;
|
||||
type IntoIter = Box<dyn Iterator<Item = Self::Item>>;
|
||||
|
||||
#[inline(always)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
match self {
|
||||
@ -245,7 +270,7 @@ impl IntoIterator for RangeCase {
|
||||
|
||||
impl RangeCase {
|
||||
/// Returns `true` if the range contains no items.
|
||||
#[inline(always)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
@ -254,7 +279,7 @@ impl RangeCase {
|
||||
}
|
||||
}
|
||||
/// Size of the range.
|
||||
#[inline(always)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn len(&self) -> INT {
|
||||
match self {
|
||||
@ -264,15 +289,56 @@ impl RangeCase {
|
||||
Self::InclusiveInt(r, ..) => *r.end() - *r.start() + 1,
|
||||
}
|
||||
}
|
||||
/// Is the specified number within this range?
|
||||
#[inline(always)]
|
||||
/// Is the specified value within this range?
|
||||
#[inline]
|
||||
#[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 {
|
||||
Self::ExclusiveInt(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?
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
@ -303,18 +369,31 @@ pub type CaseBlocksList = smallvec::SmallVec<[usize; 1]>;
|
||||
|
||||
/// _(internals)_ A type containing all cases for a `switch` statement.
|
||||
/// Exported under the `internals` feature only.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SwitchCasesCollection {
|
||||
/// List of [`ConditionalExpr`]'s.
|
||||
pub expressions: StaticVec<ConditionalExpr>,
|
||||
/// Dictionary mapping value hashes to [`ConditionalExpr`]'s.
|
||||
pub cases: BTreeMap<u64, CaseBlocksList>,
|
||||
pub cases: StraightHashMap<CaseBlocksList>,
|
||||
/// List of range cases.
|
||||
pub ranges: StaticVec<RangeCase>,
|
||||
/// Statements block for the default case (there can be no condition for the default case).
|
||||
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`].
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
const STMT_BLOCK_INLINE_SIZE: usize = 8;
|
||||
|
@ -2,7 +2,8 @@ use rhai::plugin::*;
|
||||
use rhai::{Dynamic, Engine, EvalAltResult, Module, Scope, AST, INT};
|
||||
use rustyline::config::Builder;
|
||||
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};
|
||||
|
||||
@ -188,14 +189,14 @@ fn load_script_files(engine: &mut Engine) {
|
||||
}
|
||||
|
||||
// Setup the Rustyline editor.
|
||||
fn setup_editor() -> Editor<()> {
|
||||
fn setup_editor() -> DefaultEditor {
|
||||
//env_logger::init();
|
||||
let config = Builder::new()
|
||||
.tab_stop(4)
|
||||
.indent_size(4)
|
||||
.bracketed_paste(true)
|
||||
.build();
|
||||
let mut rl = Editor::<()>::with_config(config).unwrap();
|
||||
let mut rl = DefaultEditor::with_config(config).unwrap();
|
||||
|
||||
// Bind more keys
|
||||
|
||||
@ -336,7 +337,10 @@ fn main() {
|
||||
'main_loop: loop {
|
||||
if let Some(replace) = replacement.take() {
|
||||
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;
|
||||
}
|
||||
if input.contains('\n') {
|
||||
@ -366,7 +370,9 @@ fn main() {
|
||||
if !cmd.is_empty()
|
||||
&& !cmd.starts_with('!')
|
||||
&& cmd.trim() != "history"
|
||||
&& rl.add_history_entry(input.clone())
|
||||
&& rl
|
||||
.add_history_entry(input.clone())
|
||||
.expect("Failed to add history entry")
|
||||
{
|
||||
history_offset += 1;
|
||||
}
|
||||
@ -476,7 +482,7 @@ fn main() {
|
||||
|
||||
let json = engine
|
||||
.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")
|
||||
.expect("Unable to create `metadata.json`");
|
||||
f.write_all(json.as_bytes()).expect("Unable to write data");
|
||||
@ -484,7 +490,7 @@ fn main() {
|
||||
continue;
|
||||
}
|
||||
"!!" => {
|
||||
match rl.history().last() {
|
||||
match rl.history().iter().last() {
|
||||
Some(line) => {
|
||||
replacement = Some(line.clone());
|
||||
replacement_index = history_offset + rl.history().len() - 1;
|
||||
@ -514,8 +520,12 @@ fn main() {
|
||||
_ if cmd.starts_with('!') => {
|
||||
if let Ok(num) = cmd[1..].parse::<usize>() {
|
||||
if num >= history_offset {
|
||||
if let Some(line) = rl.history().get(num - history_offset) {
|
||||
replacement = Some(line.clone());
|
||||
if let Some(line) = rl
|
||||
.history()
|
||||
.get(num - history_offset, SearchDirection::Forward)
|
||||
.expect("Failed to get history entry")
|
||||
{
|
||||
replacement = Some(line.entry.into());
|
||||
replacement_index = num;
|
||||
continue;
|
||||
}
|
||||
@ -578,7 +588,8 @@ fn main() {
|
||||
main_ast.clear_statements();
|
||||
}
|
||||
|
||||
rl.save_history(HISTORY_FILE).unwrap();
|
||||
rl.save_history(HISTORY_FILE)
|
||||
.expect("Failed to save history");
|
||||
|
||||
println!("Bye!");
|
||||
}
|
||||
|
152
src/engine.rs
152
src/engine.rs
@ -5,13 +5,11 @@ use crate::func::native::{
|
||||
locked_write, OnDebugCallback, OnDefVarCallback, OnParseTokenCallback, OnPrintCallback,
|
||||
OnVarCallback,
|
||||
};
|
||||
use crate::module::ModuleFlags;
|
||||
use crate::packages::{Package, StandardPackage};
|
||||
use crate::tokenizer::Token;
|
||||
use crate::types::StringsInterner;
|
||||
use crate::{
|
||||
Dynamic, Identifier, ImmutableString, Locked, Module, OptimizationLevel, SharedModule,
|
||||
StaticVec,
|
||||
Dynamic, Identifier, ImmutableString, Locked, OptimizationLevel, SharedModule, StaticVec,
|
||||
};
|
||||
#[cfg(feature = "no_std")]
|
||||
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.
|
||||
pub const OP_CONTAINS: &str = "contains";
|
||||
|
||||
/// Standard not operator.
|
||||
pub const OP_NOT: &str = Token::Bang.literal_syntax();
|
||||
|
||||
/// Standard exclusive range operator.
|
||||
pub const OP_EXCLUSIVE_RANGE: &str = Token::ExclusiveRange.literal_syntax();
|
||||
|
||||
/// Standard inclusive range operator.
|
||||
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.
|
||||
///
|
||||
/// # Thread Safety
|
||||
@ -96,10 +101,10 @@ pub struct Engine {
|
||||
|
||||
/// A module resolution service.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub(crate) module_resolver: Box<dyn crate::ModuleResolver>,
|
||||
pub(crate) module_resolver: Option<Box<dyn crate::ModuleResolver>>,
|
||||
|
||||
/// Strings interner.
|
||||
pub(crate) interned_strings: Locked<StringsInterner>,
|
||||
pub(crate) interned_strings: Option<Box<Locked<StringsInterner>>>,
|
||||
|
||||
/// A set of symbols to disable.
|
||||
pub(crate) disabled_symbols: Option<Box<BTreeSet<Identifier>>>,
|
||||
@ -110,7 +115,7 @@ pub struct Engine {
|
||||
/// Custom syntax.
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
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.
|
||||
pub(crate) def_var_filter: Option<Box<OnDefVarCallback>>,
|
||||
@ -120,9 +125,9 @@ pub struct Engine {
|
||||
pub(crate) token_mapper: Option<Box<OnParseTokenCallback>>,
|
||||
|
||||
/// 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.
|
||||
pub(crate) debug: Box<OnDebugCallback>,
|
||||
pub(crate) debug: Option<Box<OnDebugCallback>>,
|
||||
/// Callback closure for progress reporting.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
pub(crate) progress: Option<Box<crate::func::native::OnProgressCallback>>,
|
||||
@ -142,12 +147,10 @@ pub struct Engine {
|
||||
|
||||
/// Callback closure for debugging.
|
||||
#[cfg(feature = "debugging")]
|
||||
pub(crate) debugger_interface: Option<
|
||||
Box<(
|
||||
pub(crate) debugger_interface: Option<(
|
||||
Box<crate::eval::OnDebuggingInit>,
|
||||
Box<crate::eval::OnDebuggerCallback>,
|
||||
)>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Engine {
|
||||
@ -224,56 +227,17 @@ pub fn make_setter(id: &str) -> Identifier {
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
/// Create a new [`Engine`].
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
// Create the new scripting Engine
|
||||
let mut engine = Self::new_raw();
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
{
|
||||
engine.module_resolver = Box::new(crate::module::resolvers::FileModuleResolver::new());
|
||||
}
|
||||
|
||||
// default print/debug implementations
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
{
|
||||
engine.print = Box::new(|s| println!("{s}"));
|
||||
engine.debug = Box::new(|s, source, pos| match (source, pos) {
|
||||
(Some(source), crate::Position::NONE) => println!("{source} | {s}"),
|
||||
#[cfg(not(feature = "no_position"))]
|
||||
(Some(source), pos) => println!("{source} @ {pos:?} | {s}"),
|
||||
(None, crate::Position::NONE) => println!("{s}"),
|
||||
#[cfg(not(feature = "no_position"))]
|
||||
(None, pos) => println!("{pos:?} | {s}"),
|
||||
});
|
||||
}
|
||||
|
||||
engine.register_global_module(StandardPackage::new().as_shared_module());
|
||||
|
||||
engine
|
||||
}
|
||||
|
||||
/// Create a new [`Engine`] with minimal built-in functions.
|
||||
///
|
||||
/// Use [`register_global_module`][Engine::register_global_module] to add packages of functions.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn new_raw() -> Self {
|
||||
let mut engine = Self {
|
||||
/// 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: Box::new(crate::module::resolvers::DummyModuleResolver::new()),
|
||||
module_resolver: None,
|
||||
|
||||
interned_strings: StringsInterner::new().into(),
|
||||
interned_strings: None,
|
||||
disabled_symbols: None,
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
custom_keywords: None,
|
||||
@ -284,8 +248,8 @@ impl Engine {
|
||||
resolve_var: None,
|
||||
token_mapper: None,
|
||||
|
||||
print: Box::new(|_| {}),
|
||||
debug: Box::new(|_, _, _| {}),
|
||||
print: None,
|
||||
debug: None,
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
progress: None,
|
||||
@ -306,14 +270,55 @@ impl Engine {
|
||||
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());
|
||||
/// Create a new [`Engine`].
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
// Create the new scripting Engine
|
||||
let mut engine = Self::new_raw();
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
{
|
||||
engine.module_resolver =
|
||||
Some(Box::new(crate::module::resolvers::FileModuleResolver::new()));
|
||||
}
|
||||
|
||||
engine.interned_strings = Some(Locked::new(StringsInterner::new()).into());
|
||||
|
||||
// default print/debug implementations
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
{
|
||||
engine.print = Some(Box::new(|s| println!("{s}")));
|
||||
engine.debug = Some(Box::new(|s, source, pos| match (source, pos) {
|
||||
(Some(source), crate::Position::NONE) => println!("{source} | {s}"),
|
||||
#[cfg(not(feature = "no_position"))]
|
||||
(Some(source), pos) => println!("{source} @ {pos:?} | {s}"),
|
||||
(None, crate::Position::NONE) => println!("{s}"),
|
||||
#[cfg(not(feature = "no_position"))]
|
||||
(None, pos) => println!("{pos:?} | {s}"),
|
||||
}));
|
||||
}
|
||||
|
||||
engine.register_global_module(StandardPackage::new().as_shared_module());
|
||||
|
||||
engine
|
||||
}
|
||||
|
||||
/// Create a new [`Engine`] with minimal built-in functions.
|
||||
/// It returns a copy of [`Engine::RAW`].
|
||||
///
|
||||
/// This is useful for creating a custom scripting engine with only the functions you need.
|
||||
///
|
||||
/// Use [`register_global_module`][Engine::register_global_module] to add packages of functions.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn new_raw() -> Self {
|
||||
Self::RAW
|
||||
}
|
||||
|
||||
/// Get an interned [string][ImmutableString].
|
||||
#[cfg(not(feature = "internals"))]
|
||||
#[inline(always)]
|
||||
@ -322,7 +327,11 @@ impl Engine {
|
||||
&self,
|
||||
string: impl AsRef<str> + Into<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].
|
||||
@ -331,13 +340,17 @@ impl Engine {
|
||||
/// [`Engine`] keeps a cache of [`ImmutableString`] instances and tries to avoid new allocations
|
||||
/// when an existing instance is found.
|
||||
#[cfg(feature = "internals")]
|
||||
#[inline(always)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn get_interned_string(
|
||||
&self,
|
||||
string: impl AsRef<str> + Into<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.
|
||||
@ -354,4 +367,17 @@ impl Engine {
|
||||
pub(crate) const fn is_debugger_registered(&self) -> bool {
|
||||
self.debugger_interface.is_some()
|
||||
}
|
||||
|
||||
/// Imitation of std::hints::black_box which requires nightly.
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
#[inline(never)]
|
||||
pub(crate) fn black_box() -> usize {
|
||||
unsafe { core::ptr::read_volatile(&0_usize as *const usize) }
|
||||
}
|
||||
/// Imitation of std::hints::black_box which requires nightly.
|
||||
#[cfg(target_family = "wasm")]
|
||||
#[inline(always)]
|
||||
pub(crate) fn black_box() -> usize {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
@ -63,8 +63,8 @@ impl Caches {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn fn_resolution_cache_mut(&mut self) -> &mut FnResolutionCache {
|
||||
if self.0.is_empty() {
|
||||
// Push a new function resolution cache if the stack is empty
|
||||
if self.0.is_empty() {
|
||||
self.push_fn_resolution_cache();
|
||||
}
|
||||
self.0.last_mut().unwrap()
|
||||
|
@ -504,10 +504,8 @@ impl Engine {
|
||||
global, caches, scope, this_ptr, expr, rhs, idx_values,
|
||||
)?;
|
||||
|
||||
if !_arg_values.is_empty() {
|
||||
idx_values.extend(_arg_values);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
_ if chain_type == ChainType::Dotting => {
|
||||
|
@ -8,17 +8,16 @@ use std::borrow::Borrow;
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
|
||||
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) {
|
||||
/// 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 {
|
||||
@ -26,7 +25,7 @@ impl Dynamic {
|
||||
|
||||
match value.0 {
|
||||
Union::Array(ref a, ..) => {
|
||||
let (a, m, s) = Self::calc_array_sizes(a);
|
||||
let (a, m, s) = calc_array_sizes(a);
|
||||
ax += a;
|
||||
mx += m;
|
||||
sx += s;
|
||||
@ -34,7 +33,7 @@ impl Dynamic {
|
||||
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);
|
||||
let (a, m, s) = calc_map_sizes(m);
|
||||
ax += a;
|
||||
mx += m;
|
||||
sx += s;
|
||||
@ -49,17 +48,17 @@ impl Dynamic {
|
||||
}
|
||||
|
||||
(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) {
|
||||
}
|
||||
/// 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() {
|
||||
@ -68,7 +67,7 @@ impl Dynamic {
|
||||
match value.0 {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(ref a, ..) => {
|
||||
let (a, m, s) = Self::calc_array_sizes(a);
|
||||
let (a, m, s) = calc_array_sizes(a);
|
||||
ax += a;
|
||||
mx += m;
|
||||
sx += s;
|
||||
@ -76,7 +75,7 @@ impl Dynamic {
|
||||
#[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);
|
||||
let (a, m, s) = calc_map_sizes(m);
|
||||
ax += a;
|
||||
mx += m;
|
||||
sx += s;
|
||||
@ -91,7 +90,9 @@ impl Dynamic {
|
||||
}
|
||||
|
||||
(ax, mx, sx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Dynamic {
|
||||
/// Recursively calculate the sizes of a value.
|
||||
///
|
||||
/// 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) {
|
||||
match self.0 {
|
||||
#[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"))]
|
||||
Union::Blob(ref blob, ..) => (blob.len(), 0, 0),
|
||||
#[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()),
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
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.
|
||||
#[inline(always)]
|
||||
pub(crate) fn track_operation(
|
||||
&self,
|
||||
global: &mut GlobalRuntimeState,
|
||||
@ -198,16 +200,16 @@ impl Engine {
|
||||
global.num_operations += 1;
|
||||
|
||||
// Guard against too many operations
|
||||
let max = self.max_operations();
|
||||
|
||||
if max > 0 && global.num_operations > max {
|
||||
return Err(ERR::ErrorTooManyOperations(pos).into());
|
||||
}
|
||||
|
||||
// Report progress
|
||||
if self.max_operations() > 0 && global.num_operations > self.max_operations() {
|
||||
Err(ERR::ErrorTooManyOperations(pos).into())
|
||||
} else {
|
||||
self.progress
|
||||
.as_ref()
|
||||
.and_then(|p| p(global.num_operations))
|
||||
.map_or(Ok(()), |token| Err(ERR::ErrorTerminated(token, pos).into()))
|
||||
.and_then(|progress| {
|
||||
progress(global.num_operations)
|
||||
.map(|token| Err(ERR::ErrorTerminated(token, pos).into()))
|
||||
})
|
||||
.unwrap_or(Ok(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -510,7 +510,7 @@ impl Engine {
|
||||
let src = global.source_raw().cloned();
|
||||
let src = src.as_ref().map(|s| s.as_str());
|
||||
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());
|
||||
|
||||
|
@ -20,7 +20,7 @@ impl Engine {
|
||||
global: &GlobalRuntimeState,
|
||||
namespace: &crate::ast::Namespace,
|
||||
) -> Option<crate::SharedModule> {
|
||||
assert!(!namespace.is_empty());
|
||||
debug_assert!(!namespace.is_empty());
|
||||
|
||||
let root = namespace.root();
|
||||
|
||||
@ -74,7 +74,7 @@ impl Engine {
|
||||
if let Some(module) = self.search_imports(global, ns) {
|
||||
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(
|
||||
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(
|
||||
format!("{ns}{sep}{var_name}"),
|
||||
@ -239,9 +239,6 @@ impl Engine {
|
||||
// Coded this way for better branch prediction.
|
||||
// Popular branches are lifted out of the `match` statement into their own branches.
|
||||
|
||||
// Function calls should account for a relatively larger portion of expressions because
|
||||
// binary operators are also function calls.
|
||||
if let Expr::FnCall(x, pos) = expr {
|
||||
#[cfg(feature = "debugging")]
|
||||
let reset =
|
||||
self.run_debugger_with_reset(global, caches, scope, this_ptr.as_deref_mut(), expr)?;
|
||||
@ -250,18 +247,14 @@ impl Engine {
|
||||
|
||||
self.track_operation(global, expr.position())?;
|
||||
|
||||
// Function calls should account for a relatively larger portion of expressions because
|
||||
// binary operators are also function calls.
|
||||
if let Expr::FnCall(x, pos) = expr {
|
||||
return self.eval_fn_call_expr(global, caches, scope, this_ptr, x, *pos);
|
||||
}
|
||||
|
||||
// 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 {
|
||||
#[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 {
|
||||
this_ptr
|
||||
.ok_or_else(|| ERR::ErrorUnboundThis(*var_pos).into())
|
||||
@ -272,24 +265,26 @@ impl Engine {
|
||||
};
|
||||
}
|
||||
|
||||
#[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));
|
||||
// Then integer constants.
|
||||
if let Expr::IntegerConstant(x, ..) = expr {
|
||||
return Ok((*x).into());
|
||||
}
|
||||
|
||||
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 {
|
||||
// Constants
|
||||
Expr::DynamicConstant(x, ..) => Ok(x.as_ref().clone()),
|
||||
Expr::IntegerConstant(x, ..) => Ok((*x).into()),
|
||||
Expr::IntegerConstant(..) => unreachable!(),
|
||||
Expr::StringConstant(x, ..) => Ok(x.clone().into()),
|
||||
Expr::BoolConstant(x, ..) => Ok((*x).into()),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Expr::FloatConstant(x, ..) => Ok((*x).into()),
|
||||
Expr::StringConstant(x, ..) => Ok(x.clone().into()),
|
||||
Expr::CharConstant(x, ..) => Ok((*x).into()),
|
||||
Expr::BoolConstant(x, ..) => Ok((*x).into()),
|
||||
Expr::Unit(..) => Ok(Dynamic::UNIT),
|
||||
Expr::DynamicConstant(x, ..) => Ok(x.as_ref().clone()),
|
||||
|
||||
// `... ${...} ...`
|
||||
Expr::InterpolatedString(x, _) => {
|
||||
@ -434,8 +429,13 @@ impl Engine {
|
||||
.and_then(|r| self.check_data_size(r, expr.start_position()))
|
||||
}
|
||||
|
||||
Expr::Stmt(x) if x.is_empty() => Ok(Dynamic::UNIT),
|
||||
Expr::Stmt(x) => self.eval_stmt_block(global, caches, scope, this_ptr, x, true),
|
||||
Expr::Stmt(x) => {
|
||||
if x.is_empty() {
|
||||
Ok(Dynamic::UNIT)
|
||||
} else {
|
||||
self.eval_stmt_block(global, caches, scope, this_ptr, x, true)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(..) => {
|
||||
|
@ -127,20 +127,6 @@ impl GlobalRuntimeState {
|
||||
pub fn get_shared_import(&self, index: usize) -> Option<crate::SharedModule> {
|
||||
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.
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
|
@ -11,6 +11,12 @@ mod target;
|
||||
pub use cache::{Caches, FnResolutionCache, FnResolutionCacheEntry};
|
||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||
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")]
|
||||
pub use debugger::{
|
||||
BreakPoint, CallStackFrame, Debugger, DebuggerCommand, DebuggerEvent, DebuggerStatus,
|
||||
|
366
src/eval/stmt.rs
366
src/eval/stmt.rs
@ -3,9 +3,11 @@
|
||||
use super::{Caches, EvalContext, GlobalRuntimeState, Target};
|
||||
use crate::api::events::VarDefInfo;
|
||||
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::tokenizer::Token;
|
||||
use crate::types::dynamic::{AccessMode, Union};
|
||||
use crate::{Dynamic, Engine, RhaiResult, RhaiResultOf, Scope, ERR, INT};
|
||||
use std::hash::{Hash, Hasher};
|
||||
@ -127,48 +129,119 @@ impl Engine {
|
||||
|
||||
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 done = false;
|
||||
|
||||
// Short-circuit built-in op-assignments if under Fast Operators mode
|
||||
if self.fast_operators() {
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
use Token::*;
|
||||
|
||||
done = true;
|
||||
|
||||
// For extremely simple primary data operations, do it directly
|
||||
// to avoid the overhead of calling a function.
|
||||
match (&mut lock_guard.0, &mut new_val.0) {
|
||||
(Union::Bool(b1, ..), Union::Bool(b2, ..)) => match op_x {
|
||||
AndAssign => *b1 = *b1 && *b2,
|
||||
OrAssign => *b1 = *b1 || *b2,
|
||||
XOrAssign => *b1 = *b1 ^ *b2,
|
||||
_ => done = false,
|
||||
},
|
||||
(Union::Int(n1, ..), Union::Int(n2, ..)) => {
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
use crate::packages::arithmetic::arith_basic::INT::functions::*;
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
match op_x {
|
||||
PlusAssign => {
|
||||
*n1 = add(*n1, *n2).map_err(|err| err.fill_position(pos))?
|
||||
}
|
||||
MinusAssign => {
|
||||
*n1 = subtract(*n1, *n2).map_err(|err| err.fill_position(pos))?
|
||||
}
|
||||
MultiplyAssign => {
|
||||
*n1 = multiply(*n1, *n2).map_err(|err| err.fill_position(pos))?
|
||||
}
|
||||
DivideAssign => {
|
||||
*n1 = divide(*n1, *n2).map_err(|err| err.fill_position(pos))?
|
||||
}
|
||||
ModuloAssign => {
|
||||
*n1 = modulo(*n1, *n2).map_err(|err| err.fill_position(pos))?
|
||||
}
|
||||
_ => done = false,
|
||||
}
|
||||
#[cfg(feature = "unchecked")]
|
||||
match op_x {
|
||||
PlusAssign => *n1 += *n2,
|
||||
MinusAssign => *n1 -= *n2,
|
||||
MultiplyAssign => *n1 *= *n2,
|
||||
DivideAssign => *n1 /= *n2,
|
||||
ModuloAssign => *n1 %= *n2,
|
||||
_ => done = false,
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
(Union::Float(f1, ..), Union::Float(f2, ..)) => match op_x {
|
||||
PlusAssign => **f1 += **f2,
|
||||
MinusAssign => **f1 -= **f2,
|
||||
MultiplyAssign => **f1 *= **f2,
|
||||
DivideAssign => **f1 /= **f2,
|
||||
ModuloAssign => **f1 %= **f2,
|
||||
_ => done = false,
|
||||
},
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
(Union::Float(f1, ..), Union::Int(n2, ..)) => match op_x {
|
||||
PlusAssign => **f1 += *n2 as crate::FLOAT,
|
||||
MinusAssign => **f1 -= *n2 as crate::FLOAT,
|
||||
MultiplyAssign => **f1 *= *n2 as crate::FLOAT,
|
||||
DivideAssign => **f1 /= *n2 as crate::FLOAT,
|
||||
ModuloAssign => **f1 %= *n2 as crate::FLOAT,
|
||||
_ => done = false,
|
||||
},
|
||||
_ => done = false,
|
||||
}
|
||||
|
||||
if !done {
|
||||
if let Some((func, need_context)) =
|
||||
get_builtin_op_assignment_fn(op_x, &*lock_guard, &new_val)
|
||||
{
|
||||
// We may not need to bump the level because built-in's do not need it.
|
||||
//auto_restore! { let orig_level = global.level; global.level += 1 }
|
||||
|
||||
let args = &mut [&mut *lock_guard, &mut new_val];
|
||||
let context = need_context
|
||||
.then(|| (self, op_x_str, global.source(), &*global, pos).into());
|
||||
let _ = func(context, args).map_err(|err| err.fill_position(pos))?;
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !done {
|
||||
let opx = Some(op_x);
|
||||
let args = &mut [&mut *lock_guard, &mut new_val];
|
||||
|
||||
if self.fast_operators() {
|
||||
if let Some((func, need_context)) =
|
||||
get_builtin_op_assignment_fn(op_assign, args[0], args[1])
|
||||
{
|
||||
// Built-in found
|
||||
auto_restore! { let orig_level = global.level; global.level += 1 }
|
||||
|
||||
let context = if need_context {
|
||||
let op = op_assign.literal_syntax();
|
||||
let source = global.source();
|
||||
Some((self, op, source, &*global, pos).into())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
return func(context, args).map(|_| ());
|
||||
}
|
||||
}
|
||||
|
||||
let token = Some(op_assign);
|
||||
let op_assign = op_assign.literal_syntax();
|
||||
|
||||
match self.exec_native_fn_call(global, caches, op_assign, token, hash1, args, true, pos)
|
||||
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)) =>
|
||||
Err(err) if matches!(*err, ERR::ErrorFunctionNotFound(ref f, ..) if f.starts_with(op_x_str)) =>
|
||||
{
|
||||
// Expand to `var = var op rhs`
|
||||
let token = Some(op);
|
||||
let op = op.literal_syntax();
|
||||
let op = Some(op);
|
||||
|
||||
*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;
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
|
||||
self.check_data_size(&*args[0], root.position())?;
|
||||
}
|
||||
} else {
|
||||
// Normal assignment
|
||||
match target {
|
||||
@ -200,24 +273,20 @@ impl Engine {
|
||||
#[cfg(feature = "debugging")]
|
||||
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.
|
||||
// Popular branches are lifted out of the `match` statement into their own branches.
|
||||
|
||||
// Function calls should account for a relatively larger portion of statements.
|
||||
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);
|
||||
}
|
||||
|
||||
// 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 {
|
||||
let (op_info, BinaryExpr { lhs, rhs }) = &**x;
|
||||
|
||||
self.track_operation(global, stmt.position())?;
|
||||
|
||||
if let Expr::Variable(x, ..) = lhs {
|
||||
let rhs_val = self
|
||||
.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 {
|
||||
// No-op
|
||||
@ -293,10 +452,13 @@ impl Engine {
|
||||
.map(Dynamic::flatten),
|
||||
|
||||
// Block scope
|
||||
Stmt::Block(statements, ..) if statements.is_empty() => Ok(Dynamic::UNIT),
|
||||
Stmt::Block(statements, ..) => {
|
||||
if statements.is_empty() {
|
||||
Ok(Dynamic::UNIT)
|
||||
} else {
|
||||
self.eval_stmt_block(global, caches, scope, this_ptr, statements, true)
|
||||
}
|
||||
}
|
||||
|
||||
// If statement
|
||||
Stmt::If(x, ..) => {
|
||||
@ -311,12 +473,14 @@ impl Engine {
|
||||
.as_bool()
|
||||
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, expr.position()))?;
|
||||
|
||||
if guard_val && !if_block.is_empty() {
|
||||
match guard_val {
|
||||
true if !if_block.is_empty() => {
|
||||
self.eval_stmt_block(global, caches, scope, this_ptr, if_block, true)
|
||||
} else if !guard_val && !else_block.is_empty() {
|
||||
}
|
||||
false if !else_block.is_empty() => {
|
||||
self.eval_stmt_block(global, caches, scope, this_ptr, else_block, true)
|
||||
} else {
|
||||
Ok(Dynamic::UNIT)
|
||||
}
|
||||
_ => Ok(Dynamic::UNIT),
|
||||
}
|
||||
}
|
||||
|
||||
@ -343,7 +507,7 @@ impl Engine {
|
||||
|
||||
// First check hashes
|
||||
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 {
|
||||
let block = &expressions[index];
|
||||
@ -363,15 +527,13 @@ impl Engine {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if value.is_int() && !ranges.is_empty() {
|
||||
} else if !ranges.is_empty() {
|
||||
// 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 block = &expressions[r.index()];
|
||||
|
||||
let cond_result = match block.condition {
|
||||
Expr::BoolConstant(b, ..) => b,
|
||||
let cond_result = match condition {
|
||||
Expr::BoolConstant(b, ..) => *b,
|
||||
ref c => self
|
||||
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), c)?
|
||||
.as_bool()
|
||||
@ -381,7 +543,7 @@ impl Engine {
|
||||
};
|
||||
|
||||
if cond_result {
|
||||
result = Some(&block.expr);
|
||||
result = Some(expr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -521,12 +683,10 @@ impl Engine {
|
||||
auto_restore! { scope => rewind; let orig_scope_len = scope.len(); }
|
||||
|
||||
// Add the loop variables
|
||||
let counter_index = if counter.is_empty() {
|
||||
usize::MAX
|
||||
} else {
|
||||
let counter_index = (!counter.is_empty()).then(|| {
|
||||
scope.push(counter.name.clone(), 0 as INT);
|
||||
scope.len() - 1
|
||||
};
|
||||
});
|
||||
|
||||
scope.push(var_name.name.clone(), ());
|
||||
let index = scope.len() - 1;
|
||||
@ -535,7 +695,7 @@ impl Engine {
|
||||
|
||||
for (x, iter_value) in iter_func(iter_obj).enumerate() {
|
||||
// Increment counter
|
||||
if counter_index < usize::MAX {
|
||||
if let Some(counter_index) = counter_index {
|
||||
// As the variable increments from 0, this should always work
|
||||
// since any overflow will first be caught below.
|
||||
let index_value = x as INT;
|
||||
@ -695,94 +855,6 @@ impl Engine {
|
||||
// Empty return
|
||||
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
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Stmt::Import(x, _pos) => {
|
||||
@ -807,14 +879,16 @@ impl Engine {
|
||||
|
||||
let module = resolver
|
||||
.as_ref()
|
||||
.and_then(|r| match r.resolve_raw(self, global, &path, path_pos) {
|
||||
.and_then(
|
||||
|r| match r.resolve_raw(self, global, scope, &path, path_pos) {
|
||||
Err(err) if matches!(*err, ERR::ErrorModuleNotFound(..)) => None,
|
||||
result => Some(result),
|
||||
})
|
||||
},
|
||||
)
|
||||
.or_else(|| {
|
||||
Some(
|
||||
self.module_resolver
|
||||
.resolve_raw(self, global, &path, path_pos),
|
||||
self.module_resolver()
|
||||
.resolve_raw(self, global, scope, &path, path_pos),
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
|
@ -27,48 +27,6 @@ use rust_decimal::Decimal;
|
||||
/// The `unchecked` feature is not active.
|
||||
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`.
|
||||
#[inline(always)]
|
||||
#[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
|
||||
if x.is_variant() || y.is_variant() {
|
||||
return if is_numeric(type1) && is_numeric(type2) {
|
||||
// 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
|
||||
};
|
||||
return None;
|
||||
}
|
||||
|
||||
// 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 {
|
||||
AndAssign => impl_op!(bool = x && as_bool),
|
||||
OrAssign => impl_op!(bool = x || as_bool),
|
||||
XOrAssign => impl_op!(bool = x ^ as_bool),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
204
src/func/call.rs
204
src/func/call.rs
@ -9,6 +9,7 @@ use crate::engine::{
|
||||
};
|
||||
use crate::eval::{Caches, FnResolutionCacheEntry, GlobalRuntimeState};
|
||||
use crate::tokenizer::{is_valid_function_name, Token};
|
||||
use crate::types::dynamic::Union;
|
||||
use crate::{
|
||||
calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnArgsVec, FnPtr, ImmutableString,
|
||||
OptimizationLevel, Position, RhaiResult, RhaiResultOf, Scope, Shared, ERR,
|
||||
@ -363,7 +364,7 @@ impl Engine {
|
||||
);
|
||||
|
||||
if let Some(FnResolutionCacheEntry { func, source }) = func {
|
||||
assert!(func.is_native());
|
||||
debug_assert!(func.is_native());
|
||||
|
||||
// Push a new call stack frame
|
||||
#[cfg(feature = "debugging")]
|
||||
@ -398,11 +399,9 @@ impl Engine {
|
||||
let is_method = func.is_method();
|
||||
let src = source.as_ref().map(|s| s.as_str());
|
||||
|
||||
let context = if func.has_context() {
|
||||
Some((self, name, src, &*global, pos).into())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let context = func
|
||||
.has_context()
|
||||
.then(|| (self, name, src, &*global, pos).into());
|
||||
|
||||
let mut _result = if let Some(f) = func.get_plugin_fn() {
|
||||
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)
|
||||
return Ok(match name {
|
||||
KEYWORD_PRINT => {
|
||||
if let Some(ref print) = self.print {
|
||||
let text = result.into_immutable_string().map_err(|typ| {
|
||||
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 => {
|
||||
if let Some(ref debug) = self.debug {
|
||||
let text = result.into_immutable_string().map_err(|typ| {
|
||||
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),
|
||||
});
|
||||
@ -633,7 +640,7 @@ impl Engine {
|
||||
.cloned()
|
||||
{
|
||||
// Script function call
|
||||
assert!(func.is_script());
|
||||
debug_assert!(func.is_script());
|
||||
|
||||
let f = func.get_script_fn_def().expect("script-defined function");
|
||||
let environ = func.get_encapsulated_environ();
|
||||
@ -810,7 +817,8 @@ impl Engine {
|
||||
if call_args.is_empty() {
|
||||
let typ = self.map_type_name(target.type_name());
|
||||
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());
|
||||
return Err(self.make_type_mismatch_err::<FnPtr>(typ, first_arg_pos));
|
||||
}
|
||||
@ -1251,9 +1259,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
// Call with blank scope
|
||||
if total_args == 0 && curry.is_empty() {
|
||||
// No arguments
|
||||
} else {
|
||||
if total_args > 0 || !curry.is_empty() {
|
||||
// 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
|
||||
// avoid cloning the value
|
||||
@ -1324,9 +1330,7 @@ impl Engine {
|
||||
let args = &mut FnArgsVec::with_capacity(args_expr.len());
|
||||
let mut first_arg_value = None;
|
||||
|
||||
if args_expr.is_empty() {
|
||||
// No arguments
|
||||
} else {
|
||||
if !args_expr.is_empty() {
|
||||
// See if the first argument is a variable (not namespace-qualified).
|
||||
// If so, convert to method-call style in order to leverage potential &mut first argument
|
||||
// and avoid cloning the value
|
||||
@ -1460,11 +1464,9 @@ impl Engine {
|
||||
|
||||
Some(f) if f.is_plugin_fn() => {
|
||||
let f = f.get_plugin_fn().expect("plugin function");
|
||||
let context = if f.has_context() {
|
||||
Some((self, fn_name, module.id(), &*global, pos).into())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let context = f
|
||||
.has_context()
|
||||
.then(|| (self, fn_name, module.id(), &*global, pos).into());
|
||||
if !f.is_pure() && !args.is_empty() && args[0].is_read_only() {
|
||||
Err(ERR::ErrorNonPureMethodCallOnConstant(fn_name.to_string(), pos).into())
|
||||
} else {
|
||||
@ -1475,29 +1477,27 @@ impl Engine {
|
||||
|
||||
Some(f) if f.is_native() => {
|
||||
let func = f.get_native_fn().expect("native function");
|
||||
let context = if f.has_context() {
|
||||
Some((self, fn_name, module.id(), &*global, pos).into())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let context = f
|
||||
.has_context()
|
||||
.then(|| (self, fn_name, module.id(), &*global, pos).into());
|
||||
func(context, args).and_then(|r| self.check_data_size(r, pos))
|
||||
}
|
||||
|
||||
Some(f) => unreachable!("unknown function type: {:?}", f),
|
||||
|
||||
None => {
|
||||
let sig = if namespace.is_empty() {
|
||||
None => Err(ERR::ErrorFunctionNotFound(
|
||||
if namespace.is_empty() {
|
||||
self.gen_fn_call_signature(fn_name, args)
|
||||
} else {
|
||||
format!(
|
||||
"{namespace}{}{}",
|
||||
crate::tokenizer::Token::DoubleColon.literal_syntax(),
|
||||
crate::engine::NAMESPACE_SEPARATOR,
|
||||
self.gen_fn_call_signature(fn_name, args)
|
||||
)
|
||||
};
|
||||
|
||||
Err(ERR::ErrorFunctionNotFound(sig, pos).into())
|
||||
}
|
||||
},
|
||||
pos,
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1547,6 +1547,9 @@ impl Engine {
|
||||
/// # Main Entry-Point (`FnCallExpr`)
|
||||
///
|
||||
/// 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(
|
||||
&self,
|
||||
global: &mut GlobalRuntimeState,
|
||||
@ -1570,23 +1573,29 @@ impl Engine {
|
||||
let op_token = op_token.as_ref();
|
||||
|
||||
// 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
|
||||
.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), &args[0])?
|
||||
.0
|
||||
.flatten();
|
||||
|
||||
return value.as_bool().map(|r| (!r).into()).or_else(|_| {
|
||||
return match value.0 {
|
||||
Union::Bool(b, ..) => Ok((!b).into()),
|
||||
_ => {
|
||||
let operand = &mut [&mut value];
|
||||
self.exec_fn_call(
|
||||
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
|
||||
if op_token.is_some() && self.fast_operators() && args.len() == 2 {
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
use Token::*;
|
||||
|
||||
let mut lhs = self
|
||||
.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), &args[0])?
|
||||
.0
|
||||
@ -1597,19 +1606,128 @@ impl Engine {
|
||||
.0
|
||||
.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];
|
||||
|
||||
if let Some((func, need_context)) =
|
||||
get_builtin_binary_op_fn(op_token.as_ref().unwrap(), operands[0], operands[1])
|
||||
{
|
||||
// Built-in found
|
||||
auto_restore! { let orig_level = global.level; global.level += 1 }
|
||||
// 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 context = if need_context {
|
||||
Some((self, name.as_str(), None, &*global, pos).into())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let context =
|
||||
need_context.then(|| (self, name.as_str(), None, &*global, pos).into());
|
||||
return func(context, operands);
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,8 @@ impl Hasher for StraightHasher {
|
||||
fn finish(&self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
#[inline(always)]
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn write(&mut self, _bytes: &[u8]) {
|
||||
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.hash(s);
|
||||
s.write_usize(count);
|
||||
var_name.hash(s);
|
||||
|
||||
s.finish()
|
||||
@ -119,9 +120,9 @@ pub fn calc_fn_hash<'a>(
|
||||
}
|
||||
count += 1;
|
||||
});
|
||||
count.hash(s);
|
||||
s.write_usize(count);
|
||||
fn_name.hash(s);
|
||||
num.hash(s);
|
||||
s.write_usize(num);
|
||||
|
||||
s.finish()
|
||||
}
|
||||
@ -133,13 +134,12 @@ pub fn calc_fn_hash<'a>(
|
||||
#[must_use]
|
||||
pub fn calc_fn_hash_full(base: u64, params: impl IntoIterator<Item = TypeId>) -> u64 {
|
||||
let s = &mut get_hasher();
|
||||
base.hash(s);
|
||||
let mut count = 0;
|
||||
params.into_iter().for_each(|t| {
|
||||
t.hash(s);
|
||||
count += 1;
|
||||
});
|
||||
count.hash(s);
|
||||
s.write_usize(count);
|
||||
|
||||
s.finish()
|
||||
s.finish() ^ base
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ impl Engine {
|
||||
let orig_fn_resolution_caches_len = caches.fn_resolution_caches_len();
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
let orig_constants = if let Some(environ) = _environ {
|
||||
let orig_constants = _environ.map(|environ| {
|
||||
let EncapsulatedEnviron {
|
||||
lib,
|
||||
imports,
|
||||
@ -100,10 +100,8 @@ impl Engine {
|
||||
|
||||
global.lib.push(lib.clone());
|
||||
|
||||
Some(mem::replace(&mut global.constants, constants.clone()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
mem::replace(&mut global.constants, constants.clone())
|
||||
});
|
||||
|
||||
#[cfg(feature = "debugging")]
|
||||
if self.is_debugger_registered() {
|
||||
|
@ -85,7 +85,7 @@ pub struct FuncInfoMetadata {
|
||||
pub return_type: Identifier,
|
||||
/// Comments.
|
||||
#[cfg(feature = "metadata")]
|
||||
pub comments: Box<[Identifier]>,
|
||||
pub comments: Box<[SmartString]>,
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
/// Create a new [`Module`].
|
||||
///
|
||||
@ -361,13 +366,7 @@ impl Module {
|
||||
#[inline(always)]
|
||||
pub fn set_id(&mut self, id: impl Into<ImmutableString>) -> &mut Self {
|
||||
let id = id.into();
|
||||
|
||||
if id.is_empty() {
|
||||
self.id = None;
|
||||
} else {
|
||||
self.id = Some(id);
|
||||
}
|
||||
|
||||
self.id = (!id.is_empty()).then(|| id);
|
||||
self
|
||||
}
|
||||
|
||||
@ -691,6 +690,16 @@ impl Module {
|
||||
|
||||
if self.is_indexed() {
|
||||
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
|
||||
.get_or_insert_with(Default::default)
|
||||
.insert(hash_var, value.clone());
|
||||
@ -721,12 +730,21 @@ impl Module {
|
||||
// None + function name + number of arguments.
|
||||
let num_params = fn_def.params.len();
|
||||
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")]
|
||||
let params_info = fn_def.params.iter().map(Into::into).collect();
|
||||
|
||||
self.functions
|
||||
.get_or_insert_with(|| {
|
||||
StraightHashMap::with_capacity_and_hasher(FN_MAP_SIZE, Default::default())
|
||||
})
|
||||
.get_or_insert_with(|| new_hash_map(FN_MAP_SIZE))
|
||||
.insert(
|
||||
hash_script,
|
||||
FuncInfo {
|
||||
@ -1052,6 +1070,15 @@ impl Module {
|
||||
let hash_script = calc_fn_hash(None, name, param_types.len());
|
||||
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 {
|
||||
self.dynamic_functions_filter
|
||||
.get_or_insert_with(Default::default)
|
||||
@ -1059,9 +1086,7 @@ impl Module {
|
||||
}
|
||||
|
||||
self.functions
|
||||
.get_or_insert_with(|| {
|
||||
StraightHashMap::with_capacity_and_hasher(FN_MAP_SIZE, Default::default())
|
||||
})
|
||||
.get_or_insert_with(|| new_hash_map(FN_MAP_SIZE))
|
||||
.insert(
|
||||
hash_fn,
|
||||
FuncInfo {
|
||||
@ -1781,9 +1806,9 @@ impl Module {
|
||||
let others_len = functions.len();
|
||||
|
||||
for (&k, f) in functions.iter() {
|
||||
let map = self.functions.get_or_insert_with(|| {
|
||||
StraightHashMap::with_capacity_and_hasher(others_len, Default::default())
|
||||
});
|
||||
let map = self
|
||||
.functions
|
||||
.get_or_insert_with(|| new_hash_map(FN_MAP_SIZE));
|
||||
map.reserve(others_len);
|
||||
map.entry(k).or_insert_with(|| f.clone());
|
||||
}
|
||||
@ -2083,9 +2108,10 @@ impl Module {
|
||||
ast: &crate::AST,
|
||||
engine: &crate::Engine,
|
||||
) -> RhaiResultOf<Self> {
|
||||
let mut scope = scope;
|
||||
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].
|
||||
///
|
||||
@ -2101,13 +2127,12 @@ impl Module {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub fn eval_ast_as_new_raw(
|
||||
engine: &crate::Engine,
|
||||
scope: crate::Scope,
|
||||
scope: &mut crate::Scope,
|
||||
global: &mut crate::eval::GlobalRuntimeState,
|
||||
ast: &crate::AST,
|
||||
) -> RhaiResultOf<Self> {
|
||||
let mut scope = scope;
|
||||
|
||||
// Save global state
|
||||
let orig_scope_len = scope.len();
|
||||
let orig_imports_len = global.num_imports();
|
||||
let orig_source = global.source.clone();
|
||||
|
||||
@ -2120,7 +2145,7 @@ impl Module {
|
||||
// Run the script
|
||||
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
|
||||
let mut module = Module::new();
|
||||
@ -2162,7 +2187,9 @@ impl Module {
|
||||
});
|
||||
|
||||
// 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| {
|
||||
if let Some(fn_ptr) = v.downcast_mut::<crate::FnPtr>() {
|
||||
fn_ptr.set_encapsulated_environ(Some(environ.clone()));
|
||||
@ -2257,6 +2284,16 @@ impl Module {
|
||||
if let Some(ref v) = module.variables {
|
||||
for (var_name, value) in v.iter() {
|
||||
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());
|
||||
}
|
||||
}
|
||||
@ -2272,6 +2309,15 @@ impl Module {
|
||||
for (&hash, f) in module.functions.iter().flatten() {
|
||||
match f.metadata.namespace {
|
||||
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
|
||||
functions.insert(hash, f.func.clone());
|
||||
contains_indexed_global_functions = true;
|
||||
@ -2289,6 +2335,16 @@ impl Module {
|
||||
f.metadata.name.as_str(),
|
||||
&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());
|
||||
} else if cfg!(not(feature = "no_function")) {
|
||||
let hash_qualified_script = crate::calc_fn_hash(
|
||||
@ -2296,6 +2352,16 @@ impl Module {
|
||||
&f.metadata.name,
|
||||
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());
|
||||
}
|
||||
}
|
||||
@ -2305,14 +2371,9 @@ impl Module {
|
||||
|
||||
if !self.is_indexed() {
|
||||
let mut path = Vec::with_capacity(4);
|
||||
let mut variables = StraightHashMap::with_capacity_and_hasher(
|
||||
self.variables.as_deref().map_or(0, BTreeMap::len),
|
||||
Default::default(),
|
||||
);
|
||||
let mut functions = StraightHashMap::with_capacity_and_hasher(
|
||||
self.functions.as_ref().map_or(0, StraightHashMap::len),
|
||||
Default::default(),
|
||||
);
|
||||
let mut variables = new_hash_map(self.variables.as_deref().map_or(0, BTreeMap::len));
|
||||
let mut functions =
|
||||
new_hash_map(self.functions.as_ref().map_or(0, StraightHashMap::len));
|
||||
let mut type_iterators = BTreeMap::new();
|
||||
|
||||
path.push("");
|
||||
@ -2328,21 +2389,9 @@ impl Module {
|
||||
self.flags
|
||||
.set(ModuleFlags::INDEXED_GLOBAL_FUNCTIONS, has_global_functions);
|
||||
|
||||
self.all_variables = if variables.is_empty() {
|
||||
None
|
||||
} else {
|
||||
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.all_variables = (!variables.is_empty()).then(|| variables.into());
|
||||
self.all_functions = (!functions.is_empty()).then(|| functions.into());
|
||||
self.all_type_iterators = (!type_iterators.is_empty()).then(|| type_iterators.into());
|
||||
|
||||
self.flags |= ModuleFlags::INDEXED;
|
||||
}
|
||||
|
@ -239,14 +239,7 @@ impl FileModuleResolver {
|
||||
if !self.cache_enabled {
|
||||
return false;
|
||||
}
|
||||
|
||||
let cache = locked_read(&self.cache);
|
||||
|
||||
if cache.is_empty() {
|
||||
false
|
||||
} else {
|
||||
cache.contains_key(path.as_ref())
|
||||
}
|
||||
locked_read(&self.cache).contains_key(path.as_ref())
|
||||
}
|
||||
/// Empty the internal cache.
|
||||
#[inline]
|
||||
@ -290,15 +283,15 @@ impl FileModuleResolver {
|
||||
fn impl_resolve(
|
||||
&self,
|
||||
engine: &Engine,
|
||||
global: Option<&mut GlobalRuntimeState>,
|
||||
global: &mut GlobalRuntimeState,
|
||||
scope: &mut Scope,
|
||||
source: Option<&str>,
|
||||
path: &str,
|
||||
pos: Position,
|
||||
) -> Result<SharedModule, Box<crate::EvalAltResult>> {
|
||||
// Load relative paths from source if there is no base path specified
|
||||
let source_path = global
|
||||
.as_ref()
|
||||
.and_then(|g| g.source())
|
||||
.source()
|
||||
.or(source)
|
||||
.and_then(|p| Path::new(p).parent());
|
||||
|
||||
@ -321,12 +314,7 @@ impl FileModuleResolver {
|
||||
|
||||
ast.set_source(path);
|
||||
|
||||
let scope = Scope::new();
|
||||
|
||||
let m: Shared<_> = match global {
|
||||
Some(global) => Module::eval_ast_as_new_raw(engine, scope, global, &ast),
|
||||
None => Module::eval_ast_as_new(scope, &ast, engine),
|
||||
}
|
||||
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)))?
|
||||
.into();
|
||||
|
||||
@ -343,10 +331,11 @@ impl ModuleResolver for FileModuleResolver {
|
||||
&self,
|
||||
engine: &Engine,
|
||||
global: &mut GlobalRuntimeState,
|
||||
scope: &mut Scope,
|
||||
path: &str,
|
||||
pos: Position,
|
||||
) -> RhaiResultOf<SharedModule> {
|
||||
self.impl_resolve(engine, Some(global), None, path, pos)
|
||||
self.impl_resolve(engine, global, scope, None, path, pos)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@ -357,7 +346,9 @@ impl ModuleResolver for FileModuleResolver {
|
||||
path: &str,
|
||||
pos: Position,
|
||||
) -> 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.
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::eval::GlobalRuntimeState;
|
||||
use crate::func::SendSync;
|
||||
use crate::{Engine, Position, RhaiResultOf, SharedModule, AST};
|
||||
use crate::{Engine, Position, RhaiResultOf, Scope, SharedModule, AST};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
|
||||
@ -27,15 +27,17 @@ pub trait ModuleResolver: SendSync {
|
||||
pos: Position,
|
||||
) -> 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
|
||||
///
|
||||
/// This function is very low level.
|
||||
#[allow(unused_variables)]
|
||||
fn resolve_raw(
|
||||
&self,
|
||||
engine: &Engine,
|
||||
global: &mut GlobalRuntimeState,
|
||||
scope: &mut Scope,
|
||||
path: &str,
|
||||
pos: Position,
|
||||
) -> RhaiResultOf<SharedModule> {
|
||||
|
@ -73,12 +73,8 @@ impl StaticModuleResolver {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn contains_path(&self, path: &str) -> bool {
|
||||
if self.0.is_empty() {
|
||||
false
|
||||
} else {
|
||||
self.0.contains_key(path)
|
||||
}
|
||||
}
|
||||
/// Get an iterator of all the [modules][Module].
|
||||
#[inline]
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&str, &SharedModule)> {
|
||||
@ -123,9 +119,7 @@ impl StaticModuleResolver {
|
||||
/// Existing modules of the same path name are overwritten.
|
||||
#[inline]
|
||||
pub fn merge(&mut self, other: Self) -> &mut Self {
|
||||
if !other.is_empty() {
|
||||
self.0.extend(other.0.into_iter());
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,9 @@ use crate::ast::{
|
||||
ASTFlags, Expr, FlowControl, OpAssignment, Stmt, StmtBlock, StmtBlockContainer,
|
||||
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::func::builtin::get_builtin_binary_op_fn;
|
||||
use crate::func::hashing::get_hasher;
|
||||
@ -25,9 +27,6 @@ use std::{
|
||||
mem,
|
||||
};
|
||||
|
||||
/// Standard not operator.
|
||||
const OP_NOT: &str = Token::Bang.literal_syntax();
|
||||
|
||||
/// Level of optimization performed.
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
|
||||
#[non_exhaustive]
|
||||
@ -564,16 +563,14 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
}
|
||||
|
||||
// Then check ranges
|
||||
if value.is_int() && !ranges.is_empty() {
|
||||
let value = value.as_int().unwrap();
|
||||
|
||||
if !ranges.is_empty() {
|
||||
// Only one range or all ranges without conditions
|
||||
if ranges.len() == 1
|
||||
|| ranges
|
||||
.iter()
|
||||
.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()];
|
||||
|
||||
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();
|
||||
|
||||
ranges.retain(|r| r.contains(value));
|
||||
ranges.retain(|r| r.contains(&value));
|
||||
|
||||
if ranges.len() != old_ranges_len {
|
||||
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 let Some(result) = get_builtin_binary_op_fn(x.op_token.as_ref().unwrap(), &arg_values[0], &arg_values[1])
|
||||
.and_then(|(f, ctx)| {
|
||||
let context = if ctx {
|
||||
Some((state.engine, x.name.as_str(), None, &state.global, *pos).into())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let context = ctx.then(|| (state.engine, x.name.as_str(), None, &state.global, *pos).into());
|
||||
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();
|
||||
*expr = Expr::from_dynamic(result, *pos);
|
||||
|
@ -5,6 +5,7 @@ use crate::engine::OP_EQUALS;
|
||||
use crate::eval::{calc_index, calc_offset_len};
|
||||
use crate::module::ModuleFlags;
|
||||
use crate::plugin::*;
|
||||
|
||||
use crate::{
|
||||
def_package, Array, Dynamic, ExclusiveRange, FnPtr, InclusiveRange, NativeCallContext,
|
||||
Position, RhaiResultOf, StaticVec, ERR, INT, MAX_USIZE_INT,
|
||||
@ -237,7 +238,7 @@ pub mod array_functions {
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
if _ctx.engine().max_array_size() > 0 {
|
||||
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);
|
||||
|
||||
_ctx.engine()
|
||||
|
@ -7,6 +7,7 @@ use crate::{Dynamic, RhaiResultOf, ERR, INT};
|
||||
use std::prelude::v1::*;
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
use crate::FLOAT;
|
||||
|
||||
def_package! {
|
||||
@ -287,7 +288,8 @@ fn collect_fn_metadata(
|
||||
|
||||
#[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.
|
||||
fn scan_module(
|
||||
@ -305,7 +307,7 @@ fn collect_fn_metadata(
|
||||
use std::fmt::Write;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
109
src/parser.rs
109
src/parser.rs
@ -7,25 +7,24 @@ use crate::ast::{
|
||||
FnCallHashes, Ident, Namespace, OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock,
|
||||
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::func::{hashing::get_hasher, StraightHashMap};
|
||||
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,
|
||||
};
|
||||
use crate::types::dynamic::AccessMode;
|
||||
use crate::types::dynamic::{AccessMode, Union};
|
||||
use crate::types::StringsInterner;
|
||||
use crate::{
|
||||
calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ExclusiveRange, FnArgsVec,
|
||||
Identifier, ImmutableString, InclusiveRange, LexError, OptimizationLevel, ParseError, Position,
|
||||
Scope, Shared, SmartString, StaticVec, AST, INT, PERR,
|
||||
Scope, Shared, SmartString, StaticVec, AST, PERR,
|
||||
};
|
||||
use bitflags::bitflags;
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
convert::TryFrom,
|
||||
fmt,
|
||||
hash::{Hash, Hasher},
|
||||
@ -42,9 +41,6 @@ const SCOPE_SEARCH_BARRIER_MARKER: &str = "$ BARRIER $";
|
||||
/// The message: `TokenStream` never ends
|
||||
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.
|
||||
/// Exported under the `internals` feature only.
|
||||
pub struct ParseState<'e, 's> {
|
||||
@ -218,11 +214,7 @@ impl<'e, 's> ParseState<'e, 's> {
|
||||
self.allow_capture = true;
|
||||
}
|
||||
|
||||
let index = if hit_barrier {
|
||||
None
|
||||
} else {
|
||||
NonZeroUsize::new(index)
|
||||
};
|
||||
let index = (!hit_barrier).then(|| NonZeroUsize::new(index)).flatten();
|
||||
|
||||
(index, is_func_name)
|
||||
}
|
||||
@ -986,7 +978,7 @@ impl Engine {
|
||||
settings.pos = eat_token(input, Token::MapStart);
|
||||
|
||||
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 {
|
||||
const MISSING_RBRACE: &str = "to end this object map literal";
|
||||
@ -1121,7 +1113,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
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 def_case = None;
|
||||
let mut def_case_pos = Position::NONE;
|
||||
@ -1216,7 +1208,6 @@ impl Engine {
|
||||
let stmt_block: StmtBlock = stmt.into();
|
||||
(Expr::Stmt(stmt_block.into()), need_comma)
|
||||
};
|
||||
let has_condition = !matches!(condition, Expr::BoolConstant(true, ..));
|
||||
|
||||
expressions.push((condition, action_expr).into());
|
||||
let index = expressions.len() - 1;
|
||||
@ -1240,29 +1231,28 @@ impl Engine {
|
||||
|
||||
if let Some(mut r) = range_value {
|
||||
if !r.is_empty() {
|
||||
// Do not unroll ranges if there are previous non-unrolled ranges
|
||||
if !has_condition && ranges.is_empty() && r.len() <= SMALL_SWITCH_RANGE
|
||||
{
|
||||
// Unroll small range
|
||||
r.into_iter().for_each(|n| {
|
||||
let hasher = &mut get_hasher();
|
||||
Dynamic::from_int(n).hash(hasher);
|
||||
cases
|
||||
.entry(hasher.finish())
|
||||
.and_modify(|cases| cases.push(index))
|
||||
.or_insert_with(|| [index].into());
|
||||
});
|
||||
} else {
|
||||
// Other range
|
||||
r.set_index(index);
|
||||
ranges.push(r);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if value.is_int() && !ranges.is_empty() {
|
||||
return Err(PERR::WrongSwitchIntegerCase.into_err(expr.start_position()));
|
||||
if !ranges.is_empty() {
|
||||
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();
|
||||
@ -1299,6 +1289,10 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
expressions.shrink_to_fit();
|
||||
cases.shrink_to_fit();
|
||||
ranges.shrink_to_fit();
|
||||
|
||||
let cases = SwitchCasesCollection {
|
||||
expressions,
|
||||
cases,
|
||||
@ -1398,20 +1392,26 @@ impl Engine {
|
||||
.into(),
|
||||
)),
|
||||
// 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(
|
||||
self.parse_while_loop(input, state, lib, settings.level_up()?)?
|
||||
.into(),
|
||||
))
|
||||
}
|
||||
Token::Do if settings.has_option(LangOptions::LOOP_EXPR) => Expr::Stmt(Box::new(
|
||||
Token::Do if self.allow_looping() && settings.has_option(LangOptions::LOOP_EXPR) => {
|
||||
Expr::Stmt(Box::new(
|
||||
self.parse_do(input, state, lib, settings.level_up()?)?
|
||||
.into(),
|
||||
)),
|
||||
Token::For if settings.has_option(LangOptions::LOOP_EXPR) => Expr::Stmt(Box::new(
|
||||
))
|
||||
}
|
||||
Token::For if self.allow_looping() && settings.has_option(LangOptions::LOOP_EXPR) => {
|
||||
Expr::Stmt(Box::new(
|
||||
self.parse_for(input, state, lib, settings.level_up()?)?
|
||||
.into(),
|
||||
)),
|
||||
))
|
||||
}
|
||||
// Switch statement is allowed to act as expressions
|
||||
Token::Switch if settings.has_option(LangOptions::SWITCH_EXPR) => Expr::Stmt(Box::new(
|
||||
self.parse_switch(input, state, lib, settings.level_up()?)?
|
||||
@ -1665,7 +1665,9 @@ impl Engine {
|
||||
|
||||
match input.peek().expect(NEVER_ENDS).0 {
|
||||
// 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(
|
||||
(None, ns, 0, state.get_interned_string(*s)).into(),
|
||||
None,
|
||||
@ -1722,7 +1724,7 @@ impl Engine {
|
||||
lib: &mut FnLib,
|
||||
settings: ParseSettings,
|
||||
mut lhs: Expr,
|
||||
options: ChainingFlags,
|
||||
_options: ChainingFlags,
|
||||
) -> ParseResult<Expr> {
|
||||
let mut settings = settings;
|
||||
|
||||
@ -1783,7 +1785,7 @@ impl Engine {
|
||||
// Disallowed module separator
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
(_, token @ Token::DoubleColon)
|
||||
if options.contains(ChainingFlags::DISALLOW_NAMESPACES) =>
|
||||
if _options.contains(ChainingFlags::DISALLOW_NAMESPACES) =>
|
||||
{
|
||||
return Err(LexError::ImproperSymbol(
|
||||
token.literal_syntax().into(),
|
||||
@ -1824,7 +1826,7 @@ impl Engine {
|
||||
// Prevents capturing of the object properties as vars: xxx.<var>
|
||||
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) => {
|
||||
return Err(PERR::Reserved(s.to_string()).into_err(*pos))
|
||||
}
|
||||
@ -2352,12 +2354,7 @@ impl Engine {
|
||||
|
||||
let op = op_token.to_string();
|
||||
let hash = calc_fn_hash(None, &op, 2);
|
||||
let is_valid_script_function = is_valid_function_name(&op);
|
||||
let operator_token = if is_valid_script_function {
|
||||
None
|
||||
} else {
|
||||
Some(op_token.clone())
|
||||
};
|
||||
let native_only = !is_valid_function_name(&op);
|
||||
|
||||
let mut args = FnArgsVec::new_const();
|
||||
args.push(root);
|
||||
@ -2369,7 +2366,7 @@ impl Engine {
|
||||
name: state.get_interned_string(&op),
|
||||
hashes: FnCallHashes::from_native_only(hash),
|
||||
args,
|
||||
op_token: operator_token,
|
||||
op_token: native_only.then(|| op_token.clone()),
|
||||
capture_parent_scope: false,
|
||||
};
|
||||
|
||||
@ -2418,14 +2415,13 @@ impl Engine {
|
||||
fn_call
|
||||
} else {
|
||||
// Put a `!` call in front
|
||||
let op = Token::Bang.literal_syntax();
|
||||
let mut args = FnArgsVec::new_const();
|
||||
args.push(fn_call);
|
||||
|
||||
let not_base = FnCallExpr {
|
||||
namespace: Namespace::NONE,
|
||||
name: state.get_interned_string(op),
|
||||
hashes: FnCallHashes::from_native_only(calc_fn_hash(None, op, 1)),
|
||||
name: state.get_interned_string(OP_NOT),
|
||||
hashes: FnCallHashes::from_native_only(calc_fn_hash(None, OP_NOT, 1)),
|
||||
args,
|
||||
op_token: Some(Token::Bang),
|
||||
capture_parent_scope: false,
|
||||
@ -2442,10 +2438,10 @@ impl Engine {
|
||||
.and_then(|m| m.get(s.as_str()))
|
||||
.map_or(false, Option::is_some) =>
|
||||
{
|
||||
op_base.hashes = if is_valid_script_function {
|
||||
FnCallHashes::from_hash(calc_fn_hash(None, &s, 2))
|
||||
} else {
|
||||
op_base.hashes = if native_only {
|
||||
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)
|
||||
}
|
||||
@ -3081,8 +3077,7 @@ impl Engine {
|
||||
let (id, id_pos) = parse_var_name(input)?;
|
||||
|
||||
let (alias, alias_pos) = if match_token(input, Token::As).0 {
|
||||
let (name, pos) = parse_var_name(input)?;
|
||||
(Some(name), pos)
|
||||
parse_var_name(input).map(|(name, pos)| (Some(name), pos))?
|
||||
} else {
|
||||
(None, Position::NONE)
|
||||
};
|
||||
@ -3788,7 +3783,7 @@ impl Engine {
|
||||
(.., pos) => {
|
||||
return Err(PERR::MissingToken(
|
||||
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))
|
||||
}
|
||||
|
43
src/tests.rs
43
src/tests.rs
@ -13,6 +13,7 @@ fn check_struct_sizes() {
|
||||
feature = "only_i32",
|
||||
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::<Option<Dynamic>>(), if PACKED { 8 } else { 16 });
|
||||
@ -20,10 +21,7 @@ fn check_struct_sizes() {
|
||||
size_of::<Position>(),
|
||||
if cfg!(feature = "no_position") { 0 } else { 4 }
|
||||
);
|
||||
assert_eq!(
|
||||
size_of::<tokenizer::Token>(),
|
||||
if IS_32_BIT { 8 } else { 16 }
|
||||
);
|
||||
assert_eq!(size_of::<tokenizer::Token>(), 2 * WORD_SIZE);
|
||||
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::<ast::Stmt>(), if IS_32_BIT { 12 } else { 16 });
|
||||
@ -34,40 +32,41 @@ fn check_struct_sizes() {
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
{
|
||||
assert_eq!(
|
||||
size_of::<CallableFunction>(),
|
||||
if IS_32_BIT { 12 } else { 24 }
|
||||
);
|
||||
assert_eq!(
|
||||
size_of::<module::FuncInfo>(),
|
||||
if IS_32_BIT { 16 } else { 32 }
|
||||
);
|
||||
assert_eq!(size_of::<CallableFunction>(), 3 * WORD_SIZE);
|
||||
assert_eq!(size_of::<module::FuncInfo>(), 4 * WORD_SIZE);
|
||||
}
|
||||
|
||||
// The following only on 64-bit platforms
|
||||
|
||||
if !cfg!(target_pointer_width = "64") {
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
{
|
||||
assert_eq!(size_of::<Scope>(), 536);
|
||||
assert_eq!(
|
||||
size_of::<FnPtr>(),
|
||||
if cfg!(feature = "no_function") {
|
||||
72
|
||||
80 - if cfg!(feature = "no_function") {
|
||||
WORD_SIZE
|
||||
} else {
|
||||
80
|
||||
0
|
||||
}
|
||||
);
|
||||
assert_eq!(size_of::<LexError>(), 56);
|
||||
assert_eq!(
|
||||
size_of::<ParseError>(),
|
||||
if cfg!(feature = "no_position") { 8 } else { 16 }
|
||||
16 - if cfg!(feature = "no_position") {
|
||||
WORD_SIZE
|
||||
} else {
|
||||
0
|
||||
}
|
||||
);
|
||||
assert_eq!(size_of::<EvalAltResult>(), 64);
|
||||
assert_eq!(
|
||||
size_of::<NativeCallContext>(),
|
||||
if cfg!(feature = "no_position") {
|
||||
48
|
||||
56 - if cfg!(feature = "no_position") {
|
||||
WORD_SIZE
|
||||
} else {
|
||||
56
|
||||
0
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
612
src/tokenizer.rs
612
src/tokenizer.rs
@ -1,9 +1,6 @@
|
||||
//! Main module defining the lexer and parser.
|
||||
|
||||
use crate::engine::{
|
||||
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::engine::Precedence;
|
||||
use crate::func::native::OnParseTokenCallback;
|
||||
use crate::{Engine, Identifier, LexError, Position, SmartString, StaticVec, INT, UNSIGNED_INT};
|
||||
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 {
|
||||
/// Is the token a literal symbol?
|
||||
#[must_use]
|
||||
@ -529,101 +868,38 @@ impl Token {
|
||||
}
|
||||
|
||||
/// Reverse lookup a symbol token from a piece of syntax.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn lookup_symbol_from_syntax(syntax: &str) -> Option<Self> {
|
||||
#[allow(clippy::enum_glob_use)]
|
||||
use Token::*;
|
||||
// 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 mut hash_val = len;
|
||||
|
||||
Some(match syntax {
|
||||
"{" => LeftBrace,
|
||||
"}" => 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,
|
||||
if !(MIN_KEYWORD_LEN..=MAX_KEYWORD_LEN).contains(&len) {
|
||||
return None;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
"fn" => Fn,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
"private" => Private,
|
||||
match len {
|
||||
1 => (),
|
||||
_ => hash_val += KEYWORD_ASSOC_VALUES[(utf8[1] as usize) + 1] as usize,
|
||||
}
|
||||
hash_val += KEYWORD_ASSOC_VALUES[utf8[0] as usize] as usize;
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
"import" => Import,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
"export" => Export,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
"as" => As,
|
||||
if !(MIN_KEYWORD_HASH_VALUE..=MAX_KEYWORD_HASH_VALUE).contains(&hash_val) {
|
||||
return None;
|
||||
}
|
||||
|
||||
_ => 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
|
||||
@ -932,7 +1208,7 @@ pub fn parse_string_literal(
|
||||
}
|
||||
|
||||
loop {
|
||||
assert!(
|
||||
debug_assert!(
|
||||
!verbatim || escape.is_empty(),
|
||||
"verbatim strings should not have any escapes"
|
||||
);
|
||||
@ -1229,11 +1505,7 @@ fn get_next_token_inner(
|
||||
// Still inside a comment?
|
||||
if state.comment_level > 0 {
|
||||
let start_pos = *pos;
|
||||
let mut comment = if state.include_comments {
|
||||
Some(String::new())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut comment = state.include_comments.then(|| String::new());
|
||||
|
||||
state.comment_level =
|
||||
scan_block_comment(stream, state.comment_level, pos, comment.as_mut());
|
||||
@ -1273,8 +1545,10 @@ fn get_next_token_inner(
|
||||
pos.advance();
|
||||
|
||||
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', ..) => pos.new_line(),
|
||||
|
||||
@ -1438,16 +1712,6 @@ fn get_next_token_inner(
|
||||
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
|
||||
('"', ..) => {
|
||||
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)),
|
||||
|
||||
(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((
|
||||
Token::LexError(LERR::UnexpectedInput(ch.to_string()).into()),
|
||||
Token::LexError(LERR::UnexpectedInput(c.to_string()).into()),
|
||||
start_pos,
|
||||
))
|
||||
}
|
||||
@ -1967,7 +2236,7 @@ fn parse_identifier_token(
|
||||
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);
|
||||
}
|
||||
|
||||
@ -1981,30 +2250,6 @@ fn parse_identifier_token(
|
||||
(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?
|
||||
/// Exported under the `internals` feature only.
|
||||
#[must_use]
|
||||
@ -2030,72 +2275,71 @@ pub fn is_valid_identifier(name: &str) -> bool {
|
||||
#[must_use]
|
||||
pub fn is_valid_function_name(name: &str) -> bool {
|
||||
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()
|
||||
}
|
||||
|
||||
/// Is a character valid to start an identifier?
|
||||
#[cfg(feature = "unicode-xid-ident")]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
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?
|
||||
#[cfg(feature = "unicode-xid-ident")]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
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?
|
||||
#[cfg(not(feature = "unicode-xid-ident"))]
|
||||
#[inline(always)]
|
||||
/// Is a piece of syntax a reserved keyword or reserved symbol?
|
||||
///
|
||||
/// # 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]
|
||||
pub const fn is_id_first_alphabetic(x: char) -> bool {
|
||||
x.is_ascii_alphabetic()
|
||||
}
|
||||
pub fn is_reserved_keyword_or_symbol(syntax: &str) -> (bool, bool, bool) {
|
||||
// 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?
|
||||
#[cfg(not(feature = "unicode-xid-ident"))]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub const fn is_id_continue(x: char) -> bool {
|
||||
x.is_ascii_alphanumeric() || x == '_'
|
||||
}
|
||||
if !(MIN_RESERVED_LEN..=MAX_RESERVED_LEN).contains(&len) {
|
||||
return (false, false, false);
|
||||
}
|
||||
|
||||
/// Is a piece of syntax a reserved keyword or symbol?
|
||||
#[must_use]
|
||||
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,
|
||||
for x in 0..rounds {
|
||||
hash_val += RESERVED_ASSOC_VALUES[utf8[rounds - 1 - x] as usize] as usize;
|
||||
}
|
||||
|
||||
// List of reserved operators
|
||||
"===" | "!==" | "->" | "<-" | "?" | ":=" | ":;" | "~" | "!." | "::<" | "(*" | "*)"
|
||||
| "#" | "#!" | "@" | "$" | "++" | "--" | "..." | "<|" | "|>" => true,
|
||||
if !(MIN_RESERVED_HASH_VALUE..=MAX_RESERVED_HASH_VALUE).contains(&hash_val) {
|
||||
return (false, false, false);
|
||||
}
|
||||
|
||||
// List of reserved keywords
|
||||
"public" | "protected" | "super" | "new" | "use" | "module" | "package" | "var"
|
||||
| "static" | "shared" | "with" | "is" | "goto" | "exit" | "match" | "case" | "default"
|
||||
| "void" | "null" | "nil" | "spawn" | "thread" | "go" | "sync" | "async" | "await"
|
||||
| "yield" => true,
|
||||
|
||||
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
||||
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS | KEYWORD_IS_DEF_VAR => true,
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
crate::engine::KEYWORD_IS_DEF_FN => true,
|
||||
|
||||
_ => false,
|
||||
match RESERVED_LIST[hash_val] {
|
||||
("", ..) => (false, false, false),
|
||||
(s, true, a, b) => (
|
||||
// Fail early to avoid calling memcmp()
|
||||
// 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,
|
||||
a,
|
||||
b,
|
||||
),
|
||||
_ => (false, false, false),
|
||||
}
|
||||
}
|
||||
|
||||
|
7
src/tools/README.md
Normal file
7
src/tools/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
Build Tools
|
||||
===========
|
||||
|
||||
| File | Description |
|
||||
| -------------- | ------------------------------------------- |
|
||||
| `keywords.txt` | Input file for GNU gperf for the tokenizer. |
|
||||
| `reserved.txt` | Input file for GNU gperf for the tokenizer. |
|
102
src/tools/keywords.txt
Normal file
102
src/tools/keywords.txt
Normal file
@ -0,0 +1,102 @@
|
||||
// This file holds a list of keywords/symbols for the Rhai language, with mapping to
|
||||
// an appropriate `Token` variant.
|
||||
//
|
||||
// Generate the output table via:
|
||||
// ```bash
|
||||
// gperf -t keywords.txt
|
||||
// ```
|
||||
//
|
||||
// Since GNU gperf does not produce Rust output, the ANSI-C output must be hand-edited and
|
||||
// manually spliced into `tokenizer.rs`.
|
||||
//
|
||||
// This includes:
|
||||
// * Rewrite the C hashing program (especially since it uses a `switch` statement with fall-through)
|
||||
// into equivalent Rust as the function `lookup_symbol_from_syntax`.
|
||||
// * Update the values for the `???_KEYWORD_???` constants.
|
||||
// * Copy the `asso_values` array into `KEYWORD_ASSOC_VALUES`.
|
||||
// * Copy the `wordlist` array into `KEYWORDS_LIST` with the following modifications:
|
||||
// - Remove the `#line` comments
|
||||
// - Change the entry wrapping `{ .. }` into tuples `( .. )`
|
||||
// - Replace all entries `("")` by `("", Token::EOF)`
|
||||
// - Put feature flags on the appropriate lines, and duplicating lines that maps to `Token::EOF`
|
||||
// for the opposite feature flags
|
||||
//
|
||||
struct keyword;
|
||||
%%
|
||||
"{", Token::LeftBrace
|
||||
"}", Token::RightBrace
|
||||
"(", Token::LeftParen
|
||||
")", Token::RightParen
|
||||
"[", Token::LeftBracket
|
||||
"]", Token::RightBracket
|
||||
"()", Token::Unit
|
||||
"+", Token::Plus
|
||||
"-", Token::Minus
|
||||
"*", Token::Multiply
|
||||
"/", Token::Divide
|
||||
";", Token::SemiColon
|
||||
":", Token::Colon
|
||||
"::", Token::DoubleColon
|
||||
"=>", Token::DoubleArrow
|
||||
"_", Token::Underscore
|
||||
",", Token::Comma
|
||||
".", Token::Period
|
||||
"?.", Token::Elvis
|
||||
"??", Token::DoubleQuestion
|
||||
"?[", Token::QuestionBracket
|
||||
"..", Token::ExclusiveRange
|
||||
"..=", Token::InclusiveRange
|
||||
"#{", Token::MapStart
|
||||
"=", Token::Equals
|
||||
"true", Token::True
|
||||
"false", Token::False
|
||||
"let", Token::Let
|
||||
"const", Token::Const
|
||||
"if", Token::If
|
||||
"else", Token::Else
|
||||
"switch", Token::Switch
|
||||
"do", Token::Do
|
||||
"while", Token::While
|
||||
"until", Token::Until
|
||||
"loop", Token::Loop
|
||||
"for", Token::For
|
||||
"in", Token::In
|
||||
"!in", Token::NotIn
|
||||
"<", Token::LessThan
|
||||
">", Token::GreaterThan
|
||||
"<=", Token::LessThanEqualsTo
|
||||
">=", Token::GreaterThanEqualsTo
|
||||
"==", Token::EqualsTo
|
||||
"!=", Token::NotEqualsTo
|
||||
"!", Token::Bang
|
||||
"|", Token::Pipe
|
||||
"||", Token::Or
|
||||
"&", Token::Ampersand
|
||||
"&&", Token::And
|
||||
"continue", Token::Continue
|
||||
"break", Token::Break
|
||||
"return", Token::Return
|
||||
"throw", Token::Throw
|
||||
"try", Token::Try
|
||||
"catch", Token::Catch
|
||||
"+=", Token::PlusAssign
|
||||
"-=", Token::MinusAssign
|
||||
"*=", Token::MultiplyAssign
|
||||
"/=", Token::DivideAssign
|
||||
"<<=", Token::LeftShiftAssign
|
||||
">>=", Token::RightShiftAssign
|
||||
"&=", Token::AndAssign
|
||||
"|=", Token::OrAssign
|
||||
"^=", Token::XOrAssign
|
||||
"<<", Token::LeftShift
|
||||
">>", Token::RightShift
|
||||
"^", Token::XOr
|
||||
"%", Token::Modulo
|
||||
"%=", Token::ModuloAssign
|
||||
"**", Token::PowerOf
|
||||
"**=", Token::PowerOfAssign
|
||||
"fn", Token::Fn
|
||||
"private", Token::Private
|
||||
"import", Token::Import
|
||||
"export", Token::Export
|
||||
"as", Token::As
|
93
src/tools/reserved.txt
Normal file
93
src/tools/reserved.txt
Normal file
@ -0,0 +1,93 @@
|
||||
// This file holds a list of reserved symbols for the Rhai language.
|
||||
//
|
||||
// The mapped attributes are:
|
||||
// - is this a reserved symbol? (bool)
|
||||
// - can this keyword be called normally as a function? (bool)
|
||||
// - can this keyword be called in method-call style? (bool)
|
||||
//
|
||||
// Generate the output table via:
|
||||
// ```bash
|
||||
// gperf -t reserved.txt
|
||||
// ```
|
||||
//
|
||||
// Since GNU gperf does not produce Rust output, the ANSI-C output must be hand-edited and
|
||||
// manually spliced into `tokenizer.rs`.
|
||||
//
|
||||
// This includes:
|
||||
// * Rewrite the C hashing program (especially since it uses a `switch` statement with fall-through)
|
||||
// into equivalent Rust as the function `is_reserved_keyword_or_symbol`.
|
||||
// * Update the values for the `???_RESERVED_???` constants.
|
||||
// * Copy the `asso_values` array into `RESERVED_ASSOC_VALUES`.
|
||||
// * Copy the `wordlist` array into `RESERVED_LIST` with the following modifications:
|
||||
// - Remove the `#line` comments
|
||||
// - Change the entry wrapping `{ .. }` into tuples `( .. )`
|
||||
// - Replace all entries `("")` by `("", false, false, false)`
|
||||
// - Feature flags can be incorporated directly into the output via the `cfg!` macro
|
||||
//
|
||||
struct reserved;
|
||||
%%
|
||||
"?.", cfg!(feature = "no_object"), false, false
|
||||
"?[", cfg!(feature = "no_index"), false, false
|
||||
"fn", cfg!(feature = "no_function"), false, false
|
||||
"private", cfg!(feature = "no_function"), false, false
|
||||
"import", cfg!(feature = "no_module"), false, false
|
||||
"export", cfg!(feature = "no_module"), false, false
|
||||
"as", cfg!(feature = "no_module"), false, false
|
||||
"===", true, false, false
|
||||
"!==", true, false, false
|
||||
"->", true, false, false
|
||||
"<-", true, false, false
|
||||
"?", true, false, false
|
||||
":=", true, false, false
|
||||
":;", true, false, false
|
||||
"~", true, false, false
|
||||
"!.", true, false, false
|
||||
"::<", true, false, false
|
||||
"(*", true, false, false
|
||||
"*)", true, false, false
|
||||
"#", true, false, false
|
||||
"#!", true, false, false
|
||||
"@", true, false, false
|
||||
"$", true, false, false
|
||||
"++", true, false, false
|
||||
"--", true, false, false
|
||||
"...", true, false, false
|
||||
"<|", true, false, false
|
||||
"|>", true, false, false
|
||||
"public", true, false, false
|
||||
"protected", true, false, false
|
||||
"super", true, false, false
|
||||
"new", true, false, false
|
||||
"use", true, false, false
|
||||
"module", true, false, false
|
||||
"package", true, false, false
|
||||
"var", true, false, false
|
||||
"static", true, false, false
|
||||
"shared", true, false, false
|
||||
"with", true, false, false
|
||||
"is", true, false, false
|
||||
"goto", true, false, false
|
||||
"exit", true, false, false
|
||||
"match", true, false, false
|
||||
"case", true, false, false
|
||||
"default", true, false, false
|
||||
"void", true, false, false
|
||||
"null", true, false, false
|
||||
"nil", true, false, false
|
||||
"spawn", true, false, false
|
||||
"thread", true, false, false
|
||||
"go", true, false, false
|
||||
"sync", true, false, false
|
||||
"async", true, false, false
|
||||
"await", true, false, false
|
||||
"yield", true, false, false
|
||||
"print", true, true, false
|
||||
"debug", true, true, false
|
||||
"type_of", true, true, true
|
||||
"eval", true, true, false
|
||||
"Fn", true, true, false
|
||||
"call", true, true, true
|
||||
"curry", true, true, true
|
||||
"this", true, false, false
|
||||
"is_def_var", true, true, false
|
||||
"is_def_fn", cfg!(not(feature = "no_function")), true, false
|
@ -538,7 +538,7 @@ impl TryFrom<ImmutableString> for FnPtr {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
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()
|
||||
{
|
||||
Err(
|
||||
|
@ -105,7 +105,7 @@ pub enum ParseErrorType {
|
||||
DuplicatedSwitchCase,
|
||||
/// A variable name is duplicated. Wrapped value is the variable name.
|
||||
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,
|
||||
/// The default case of a `switch` statement is in an appropriate place.
|
||||
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) => write!(f, "'{s}' is a reserved symbol"),
|
||||
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::WrongSwitchCaseCondition => f.write_str("This switch case cannot have a condition"),
|
||||
Self::PropertyExpected => f.write_str("Expecting name of a property"),
|
||||
|
@ -385,11 +385,23 @@ impl Scope<'_> {
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn pop(&mut self) -> &mut Self {
|
||||
self.names.pop().expect("`Scope` must not be empty");
|
||||
let _ = self.values.pop().expect("`Scope` must not be empty");
|
||||
self.aliases.pop().expect("`Scope` must not be empty");
|
||||
self.names.pop().expect("not empty");
|
||||
let _ = self.values.pop().expect("not empty");
|
||||
self.aliases.pop().expect("not empty");
|
||||
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.
|
||||
///
|
||||
/// # Example
|
||||
|
@ -515,14 +515,12 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
|
||||
3
|
||||
);
|
||||
|
||||
engine
|
||||
.eval::<()>(
|
||||
engine.eval::<()>(
|
||||
"
|
||||
let x = [1, 2, 3, 2, 1];
|
||||
x.find(|v| v > 4)
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
)?;
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
@ -534,14 +532,12 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
|
||||
2
|
||||
);
|
||||
|
||||
engine
|
||||
.eval::<()>(
|
||||
engine.eval::<()>(
|
||||
"
|
||||
let x = [#{alice: 1}, #{bob: 2}, #{clara: 3}];
|
||||
x.find_map(|v| v.dave)
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -550,7 +546,7 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
|
||||
fn test_arrays_elvis() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
engine.eval::<()>("let x = (); x?[2]").unwrap();
|
||||
engine.eval::<()>("let x = (); x?[2]")?;
|
||||
|
||||
engine.run("let x = (); x?[2] = 42")?;
|
||||
|
||||
|
@ -4,8 +4,8 @@ use rhai::{Engine, EvalAltResult};
|
||||
fn test_bool_op1() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
assert!(engine.eval::<bool>("true && (false || true)").unwrap());
|
||||
assert!(engine.eval::<bool>("true & (false | true)").unwrap());
|
||||
assert!(engine.eval::<bool>("true && (false || true)")?);
|
||||
assert!(engine.eval::<bool>("true & (false | true)")?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -14,8 +14,8 @@ fn test_bool_op1() -> Result<(), Box<EvalAltResult>> {
|
||||
fn test_bool_op2() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
assert!(!engine.eval::<bool>("false && (false || true)").unwrap());
|
||||
assert!(!engine.eval::<bool>("false & (false | true)").unwrap());
|
||||
assert!(!engine.eval::<bool>("false && (false || true)")?);
|
||||
assert!(!engine.eval::<bool>("false & (false | true)")?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -25,9 +25,9 @@ fn test_bool_op3() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
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>("false && (true || { throw })").unwrap());
|
||||
assert!(!engine.eval::<bool>("false && (true || { throw })")?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -36,23 +36,19 @@ fn test_bool_op3() -> Result<(), Box<EvalAltResult>> {
|
||||
fn test_bool_op_short_circuit() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
assert!(engine
|
||||
.eval::<bool>(
|
||||
assert!(engine.eval::<bool>(
|
||||
"
|
||||
let x = true;
|
||||
x || { throw; };
|
||||
"
|
||||
)
|
||||
.unwrap());
|
||||
)?);
|
||||
|
||||
assert!(!engine
|
||||
.eval::<bool>(
|
||||
assert!(!engine.eval::<bool>(
|
||||
"
|
||||
let x = false;
|
||||
x && { throw; };
|
||||
"
|
||||
)
|
||||
.unwrap());
|
||||
)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -369,9 +369,9 @@ fn test_closures_external() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
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(())
|
||||
}
|
||||
@ -383,20 +383,20 @@ fn test_closures_callback() -> Result<(), Box<EvalAltResult>> {
|
||||
type SingleNode = Rc<dyn Node>;
|
||||
|
||||
trait Node {
|
||||
fn run(&self, x: INT) -> INT;
|
||||
fn run(&self, x: INT) -> Result<INT, Box<EvalAltResult>>;
|
||||
}
|
||||
|
||||
struct PhaserNode {
|
||||
func: Box<dyn Fn(INT) -> INT>,
|
||||
func: Box<dyn Fn(INT) -> Result<INT, Box<EvalAltResult>>>,
|
||||
}
|
||||
|
||||
impl Node for PhaserNode {
|
||||
fn run(&self, x: INT) -> INT {
|
||||
fn run(&self, x: INT) -> Result<INT, Box<EvalAltResult>> {
|
||||
(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 {
|
||||
func: Box::new(callback),
|
||||
}
|
||||
@ -419,7 +419,7 @@ fn test_closures_callback() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = engine2.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
|
||||
});
|
||||
@ -428,7 +428,7 @@ fn test_closures_callback() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
let cb = shared_engine.borrow().eval_ast::<SingleNode>(&ast)?;
|
||||
|
||||
assert_eq!(cb.run(21), 42);
|
||||
assert_eq!(cb.run(21)?, 42);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ fn test_comments() -> Result<(), Box<EvalAltResult>> {
|
||||
42
|
||||
);
|
||||
|
||||
engine.run("/* Hello world */").unwrap();
|
||||
engine.run("/* Hello world */")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -5,8 +5,8 @@ fn test_or_equals() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
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 = false; x |= true; x").unwrap());
|
||||
assert!(engine.eval::<bool>("let x = true; x |= false; x")?);
|
||||
assert!(engine.eval::<bool>("let x = false; x |= true; x")?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -16,9 +16,9 @@ fn test_and_equals() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
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 = false; x &= true; x").unwrap());
|
||||
assert!(engine.eval::<bool>("let x = true; x &= true; x").unwrap());
|
||||
assert!(!engine.eval::<bool>("let x = true; x &= false; x")?);
|
||||
assert!(!engine.eval::<bool>("let x = false; x &= true; x")?);
|
||||
assert!(engine.eval::<bool>("let x = true; x &= true; x")?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -251,6 +251,62 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
||||
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]
|
||||
fn test_custom_syntax_raw() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
|
@ -50,8 +50,8 @@ fn test_expressions() -> Result<(), Box<EvalAltResult>> {
|
||||
"
|
||||
switch x {
|
||||
0 => 1,
|
||||
1..10 => 123,
|
||||
10 => 42,
|
||||
1..10 => 123,
|
||||
}
|
||||
"
|
||||
)?,
|
||||
@ -63,11 +63,11 @@ fn test_expressions() -> Result<(), Box<EvalAltResult>> {
|
||||
"
|
||||
switch x {
|
||||
0 => 1,
|
||||
10 => 42,
|
||||
1..10 => {
|
||||
let y = 123;
|
||||
y
|
||||
}
|
||||
10 => 42,
|
||||
}
|
||||
"
|
||||
)
|
||||
|
@ -9,9 +9,7 @@ fn test_float() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
assert!(engine.eval::<bool>("let x = 0.0; let y = 1.0; x < y")?);
|
||||
assert!(!engine.eval::<bool>("let x = 0.0; let y = 1.0; x > y")?);
|
||||
assert!(!engine
|
||||
.eval::<bool>("let x = 0.; let y = 1.; x > y")
|
||||
.unwrap());
|
||||
assert!(!engine.eval::<bool>("let x = 0.; let y = 1.; x > y")?);
|
||||
assert!((engine.eval::<FLOAT>("let x = 9.9999; x")? - 9.9999 as FLOAT).abs() < EPSILON);
|
||||
|
||||
Ok(())
|
||||
|
@ -399,11 +399,9 @@ fn test_get_set_indexer() -> Result<(), Box<EvalAltResult>> {
|
||||
fn test_get_set_elvis() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
engine.eval::<()>("let x = (); x?.foo.bar.baz").unwrap();
|
||||
engine.eval::<()>("let x = (); x?.foo(1,2,3)").unwrap();
|
||||
engine
|
||||
.eval::<()>("let x = #{a:()}; x.a?.foo.bar.baz")
|
||||
.unwrap();
|
||||
engine.eval::<()>("let x = (); x?.foo.bar.baz")?;
|
||||
engine.eval::<()>("let x = (); x?.foo(1,2,3)")?;
|
||||
engine.eval::<()>("let x = #{a:()}; x.a?.foo.bar.baz")?;
|
||||
assert_eq!(engine.eval::<String>("let x = 'x'; x?.type_of()")?, "char");
|
||||
|
||||
Ok(())
|
||||
|
@ -20,7 +20,7 @@ fn test_loop() -> Result<(), Box<EvalAltResult>> {
|
||||
}
|
||||
}
|
||||
|
||||
return x;
|
||||
x
|
||||
"
|
||||
)?,
|
||||
21
|
||||
@ -53,3 +53,43 @@ fn test_loop() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_loop_expression() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
"
|
||||
let x = 0;
|
||||
|
||||
let value = while x < 10 {
|
||||
if x % 5 == 0 { break 42; }
|
||||
x += 1;
|
||||
};
|
||||
|
||||
value
|
||||
"
|
||||
)?,
|
||||
42
|
||||
);
|
||||
|
||||
engine.set_allow_loop_expressions(false);
|
||||
|
||||
assert!(engine
|
||||
.eval::<INT>(
|
||||
"
|
||||
let x = 0;
|
||||
|
||||
let value = while x < 10 {
|
||||
if x % 5 == 0 { break 42; }
|
||||
x += 1;
|
||||
};
|
||||
|
||||
value
|
||||
"
|
||||
)
|
||||
.is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -39,7 +39,10 @@ fn test_mismatched_op_custom_type() -> Result<(), Box<EvalAltResult>> {
|
||||
").expect_err("should error"),
|
||||
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!(
|
||||
*engine.eval::<INT>("60 + new_ts()").expect_err("should error"),
|
||||
|
@ -4,14 +4,12 @@ use rhai::{Engine, EvalAltResult};
|
||||
fn test_not() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
assert!(!engine
|
||||
.eval::<bool>("let not_true = !true; not_true")
|
||||
.unwrap());
|
||||
assert!(!engine.eval::<bool>("let not_true = !true; not_true")?);
|
||||
|
||||
#[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(())
|
||||
}
|
||||
|
27
tests/ops.rs
27
tests/ops.rs
@ -35,7 +35,11 @@ fn test_ops_other_number_types() -> Result<(), Box<EvalAltResult>> {
|
||||
EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("== (u16,")
|
||||
));
|
||||
|
||||
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(())
|
||||
}
|
||||
@ -63,3 +67,24 @@ fn test_ops_precedence() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_custom_types() -> Result<(), Box<EvalAltResult>> {
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct Test1;
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct Test2;
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine
|
||||
.register_type_with_name::<Test1>("Test1")
|
||||
.register_type_with_name::<Test2>("Test2")
|
||||
.register_fn("new_ts1", || Test1)
|
||||
.register_fn("new_ts2", || Test2)
|
||||
.register_fn("==", |x: Test1, y: Test2| true);
|
||||
|
||||
assert!(engine.eval::<bool>("let x = new_ts1(); let y = new_ts2(); x == y")?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -89,21 +89,21 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
assert_eq!(
|
||||
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 }")?;
|
||||
|
||||
assert_eq!(
|
||||
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 }")?;
|
||||
|
||||
assert_eq!(
|
||||
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);
|
||||
@ -112,14 +112,14 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
assert_eq!(
|
||||
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")?;
|
||||
|
||||
assert_eq!(
|
||||
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();
|
||||
@ -131,7 +131,7 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
assert_eq!(
|
||||
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(())
|
||||
|
@ -10,9 +10,7 @@ fn test_switch() -> Result<(), Box<EvalAltResult>> {
|
||||
engine.eval::<char>("switch 2 { 1 => (), 2 => 'a', 42 => true }")?,
|
||||
'a'
|
||||
);
|
||||
engine
|
||||
.run("switch 3 { 1 => (), 2 => 'a', 42 => true }")
|
||||
.unwrap();
|
||||
engine.run("switch 3 { 1 => (), 2 => 'a', 42 => true }")?;
|
||||
assert_eq!(
|
||||
engine.eval::<INT>("switch 3 { 1 => (), 2 => 'a', 42 => true, _ => 123 }")?,
|
||||
123
|
||||
@ -31,15 +29,13 @@ fn test_switch() -> Result<(), Box<EvalAltResult>> {
|
||||
)?,
|
||||
'a'
|
||||
);
|
||||
assert!(engine
|
||||
.eval_with_scope::<bool>(&mut scope, "switch x { 1 => (), 2 => 'a', 42 => true }")
|
||||
.unwrap());
|
||||
assert!(engine
|
||||
.eval_with_scope::<bool>(&mut scope, "switch x { 1 => (), 2 => 'a', _ => true }")
|
||||
.unwrap());
|
||||
let _: () = engine
|
||||
.eval_with_scope::<()>(&mut scope, "switch x { 1 => 123, 2 => 'a' }")
|
||||
.unwrap();
|
||||
assert!(
|
||||
engine.eval_with_scope::<bool>(&mut scope, "switch x { 1 => (), 2 => 'a', 42 => true }")?
|
||||
);
|
||||
assert!(
|
||||
engine.eval_with_scope::<bool>(&mut scope, "switch x { 1 => (), 2 => 'a', _ => true }")?
|
||||
);
|
||||
let _: () = engine.eval_with_scope::<()>(&mut scope, "switch x { 1 => 123, 2 => 'a' }")?;
|
||||
|
||||
assert_eq!(
|
||||
engine.eval_with_scope::<INT>(
|
||||
@ -276,6 +272,13 @@ fn test_switch_ranges() -> Result<(), Box<EvalAltResult>> {
|
||||
).expect_err("should error").err_type(),
|
||||
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!(
|
||||
engine.eval_with_scope::<char>(
|
||||
&mut scope,
|
||||
|
@ -10,9 +10,7 @@ fn test_unit() -> Result<(), Box<EvalAltResult>> {
|
||||
#[test]
|
||||
fn test_unit_eq() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
assert!(engine
|
||||
.eval::<bool>("let x = (); let y = (); x == y")
|
||||
.unwrap());
|
||||
assert!(engine.eval::<bool>("let x = (); let y = (); x == y")?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user