Merge pull request #676 from schungx/master

Code refactor.
This commit is contained in:
Stephen Chung 2022-12-04 23:11:22 +08:00 committed by GitHub
commit 252567f84d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
85 changed files with 39304 additions and 2337 deletions

View File

@ -4,14 +4,37 @@ Rhai Release Notes
Version 1.12.0 Version 1.12.0
============== ==============
Buf fixes Bug fixes
--------- ---------
* Integer numbers that are too large to deserialize into `INT` now fall back to `Decimal` or `FLOAT` instead of silently truncating. * Integer numbers that are too large to deserialize into `INT` now fall back to `Decimal` or `FLOAT` instead of silently truncating.
* Parsing deeply-nested closures (e.g. `||{||{||{||{||{||{||{...}}}}}}}`) no longer panics but will be confined to the nesting limit.
Breaking API changes
--------------------
* The callback for initializing a debugger instance has changed to `Fn(&Engine, Debugger) -> Debugger`. This allows more control over the initial setup of the debugger.
* The internal macro `reify!` is no longer available publicly.
Deprecated API's
----------------
* `Module::with_capacity` is deprecated.
* The internal method `Engine::eval_statements_raw` is deprecated.
Speed improvements
------------------
* The functions registration mechanism is revamped to take advantage of constant generics, among others.
* This yields a 10-20% speed improvements on certain real-life, function-call-heavy workloads.
Net features Net features
------------ ------------
### `!in`
* A new operator `!in` is added which maps to `!(... in ...)`.
### `Engine::call_fn_with_options` ### `Engine::call_fn_with_options`
* `Engine::call_fn_raw` is deprecated in favor of `Engine::call_fn_with_options` which allows setting options for the function call. * `Engine::call_fn_raw` is deprecated in favor of `Engine::call_fn_with_options` which allows setting options for the function call.
@ -21,11 +44,15 @@ Net features
Enhancements Enhancements
------------ ------------
* Optimizations have been done to key data structures to minimize size and creation time, which involves turning rarely-used fields into `Option<Box<T>>`. This resulted in some speed improvements.
* `CallableFunction` is exported under `internals`. * `CallableFunction` is exported under `internals`.
* The `TypeBuilder` type and `CustomType` trait are no longer marked as volatile. * The `TypeBuilder` type and `CustomType` trait are no longer marked as volatile.
* `FuncArgs` is also implemented for arrays. * `FuncArgs` is also implemented for arrays.
* `Engine::set_XXX` API can now be chained. * `Engine::set_XXX` API can now be chained.
* `EvalContext::scope_mut` now returns `&mut Scope` instead of `&mut &mut Scope`. * `EvalContext::scope_mut` now returns `&mut Scope` instead of `&mut &mut Scope`.
* Line-style doc-comments are now merged into a single string to avoid creating many strings. Block-style doc-comments continue to be independent strings.
* Block-style doc-comments are now "un-indented" for better formatting.
* Doc-comments on plugin modules are now captured in the module's `doc` field.
Version 1.11.0 Version 1.11.0

View File

@ -23,7 +23,7 @@ ahash = { version = "0.8.2", default-features = false, features = ["compile-time
num-traits = { version = "0.2", default-features = false } num-traits = { version = "0.2", default-features = false }
bitflags = { version = "1", default-features = false } bitflags = { version = "1", default-features = false }
smartstring = { version = "1", default-features = false } smartstring = { version = "1", default-features = false }
rhai_codegen = { version = "1.4.1", path = "codegen", default-features = false } rhai_codegen = { version = "1.5.0", path = "codegen", default-features = false }
no-std-compat = { version = "0.4", default-features = false, features = ["alloc"], optional = true } no-std-compat = { version = "0.4", default-features = false, features = ["alloc"], optional = true }
libm = { version = "0.2", default-features = false, optional = true } libm = { version = "0.2", default-features = false, optional = true }
@ -43,7 +43,7 @@ serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
[features] [features]
default = ["std", "ahash/runtime-rng"] # ahash/runtime-rng trumps ahash/compile-time-rng default = ["std", "ahash/runtime-rng"] # ahash/runtime-rng trumps ahash/compile-time-rng
std = ["ahash/std", "num-traits/std", "smartstring/std"] std = ["ahash/std", "num-traits/std", "smartstring/std"]
unchecked = [] # unchecked arithmetic unchecked = [] # disable safety checks
sync = [] # restrict to only types that implement Send + Sync sync = [] # restrict to only types that implement Send + Sync
no_position = [] # do not track position in the parser no_position = [] # do not track position in the parser
no_optimize = [] # no script optimizer no_optimize = [] # no script optimizer

View File

@ -33,7 +33,7 @@ Standard features
----------------- -----------------
* Simple language similar to JavaScript+Rust with [dynamic](https://rhai.rs/book/language/dynamic.html) typing. * Simple language similar to JavaScript+Rust with [dynamic](https://rhai.rs/book/language/dynamic.html) typing.
* Fairly efficient evaluation (1 million iterations in 0.23 sec on a single-core, 2.3 GHz Linux VM). * Fairly efficient evaluation (1 million iterations in 0.14 sec on a single-core 2.6 GHz Linux VM).
* Tight integration with native Rust [functions](https://rhai.rs/book/rust/functions.html) and [types](https://rhai.rs/book/rust/custom-types.html), including [getters/setters](https://rhai.rs/book/rust/getters-setters.html), [methods](https://rhai.rs/book/rust/methods.html) and [indexers](https://rhai.rs/book/rust/indexers.html). * Tight integration with native Rust [functions](https://rhai.rs/book/rust/functions.html) and [types](https://rhai.rs/book/rust/custom-types.html), including [getters/setters](https://rhai.rs/book/rust/getters-setters.html), [methods](https://rhai.rs/book/rust/methods.html) and [indexers](https://rhai.rs/book/rust/indexers.html).
* Freely pass Rust values into a script as [variables](https://rhai.rs/book/language/variables.html)/[constants](https://rhai.rs/book/language/constants.html) via an external [`Scope`](https://rhai.rs/book/engine/scope.html) - all clonable Rust types are supported; no need to implement any special trait. Or tap directly into the [variable resolution process](https://rhai.rs/book/engine/var.html). * Freely pass Rust values into a script as [variables](https://rhai.rs/book/language/variables.html)/[constants](https://rhai.rs/book/language/constants.html) via an external [`Scope`](https://rhai.rs/book/engine/scope.html) - all clonable Rust types are supported; no need to implement any special trait. Or tap directly into the [variable resolution process](https://rhai.rs/book/engine/var.html).
* Built-in support for most common [data types](https://rhai.rs/book/language/values-and-types.html) including booleans, [integers](https://rhai.rs/book/language/numbers.html), [floating-point numbers](https://rhai.rs/book/language/numbers.html) (including [`Decimal`](https://crates.io/crates/rust_decimal)), [strings](https://rhai.rs/book/language/strings-chars.html), [Unicode characters](https://rhai.rs/book/language/strings-chars.html), [arrays](https://rhai.rs/book/language/arrays.html) (including packed [byte arrays](https://rhai.rs/book/language/blobs.html)) and [object maps](https://rhai.rs/book/language/object-maps.html). * Built-in support for most common [data types](https://rhai.rs/book/language/values-and-types.html) including booleans, [integers](https://rhai.rs/book/language/numbers.html), [floating-point numbers](https://rhai.rs/book/language/numbers.html) (including [`Decimal`](https://crates.io/crates/rust_decimal)), [strings](https://rhai.rs/book/language/strings-chars.html), [Unicode characters](https://rhai.rs/book/language/strings-chars.html), [arrays](https://rhai.rs/book/language/arrays.html) (including packed [byte arrays](https://rhai.rs/book/language/blobs.html)) and [object maps](https://rhai.rs/book/language/object-maps.html).

View File

@ -1,11 +1,11 @@
[package] [package]
name = "rhai_codegen" name = "rhai_codegen"
version = "1.4.3" version = "1.5.0"
edition = "2018" edition = "2018"
resolver = "2" resolver = "2"
authors = ["jhwgh1968", "Stephen Chung"] authors = ["jhwgh1968", "Stephen Chung"]
description = "Procedural macros support package for Rhai, a scripting language and engine for Rust" description = "Procedural macros support package for Rhai, a scripting language and engine for Rust"
keywords = ["rhai", "scripting", "scripting-engine", "scripting-language", "embedded", "plugin", "macros", "code-generation"] keywords = ["scripting", "scripting-engine", "scripting-language", "embedded", "plugin"]
categories = ["no-std", "embedded", "wasm", "parser-implementations"] categories = ["no-std", "embedded", "wasm", "parser-implementations"]
homepage = "https://rhai.rs/book/plugins/index.html" homepage = "https://rhai.rs/book/plugins/index.html"
repository = "https://github.com/rhaiscript/rhai" repository = "https://github.com/rhaiscript/rhai"
@ -24,5 +24,5 @@ syn = { version = "1.0", features = ["full", "parsing", "printing", "proc-macro"
quote = "1" quote = "1"
[dev-dependencies] [dev-dependencies]
rhai = { path = "..", version = "1.11", features = ["metadata"] } rhai = { path = "..", version = "1.12", features = ["metadata"] }
trybuild = "1" trybuild = "1"

View File

@ -145,6 +145,7 @@ pub fn inner_item_attributes<T: ExportedParams>(
pub fn doc_attributes(attrs: &[syn::Attribute]) -> syn::Result<Vec<String>> { pub fn doc_attributes(attrs: &[syn::Attribute]) -> syn::Result<Vec<String>> {
// Find the #[doc] attribute which will turn be read for function documentation. // Find the #[doc] attribute which will turn be read for function documentation.
let mut comments = Vec::new(); let mut comments = Vec::new();
let mut buf = String::new();
for attr in attrs { for attr in attrs {
if let Some(i) = attr.path.get_ident() { if let Some(i) = attr.path.get_ident() {
@ -158,19 +159,30 @@ pub fn doc_attributes(attrs: &[syn::Attribute]) -> syn::Result<Vec<String>> {
if line.contains('\n') { if line.contains('\n') {
// Must be a block comment `/** ... */` // Must be a block comment `/** ... */`
if !buf.is_empty() {
comments.push(buf.clone());
buf.clear();
}
line.insert_str(0, "/**"); line.insert_str(0, "/**");
line.push_str("*/"); line.push_str("*/");
comments.push(line);
} else { } else {
// Single line - assume it is `///` // Single line - assume it is `///`
line.insert_str(0, "///"); if !buf.is_empty() {
buf.push('\n');
}
buf.push_str("///");
buf.push_str(&line);
} }
comments.push(line);
} }
} }
} }
} }
if !buf.is_empty() {
comments.push(buf);
}
Ok(comments) Ok(comments)
} }

View File

@ -674,6 +674,7 @@ impl ExportedFn {
let arg_count = self.arg_count(); let arg_count = self.arg_count();
let is_method_call = self.mutable_receiver(); let is_method_call = self.mutable_receiver();
let is_pure = !self.mutable_receiver() || self.params().pure.is_some(); let is_pure = !self.mutable_receiver() || self.params().pure.is_some();
let pass_context = self.pass_context;
let mut unpack_statements = Vec::new(); let mut unpack_statements = Vec::new();
let mut unpack_exprs = Vec::new(); let mut unpack_exprs = Vec::new();
@ -689,7 +690,7 @@ impl ExportedFn {
let skip_first_arg; let skip_first_arg;
if self.pass_context { if self.pass_context {
unpack_exprs.push(syn::parse2::<syn::Expr>(quote! { context }).unwrap()); unpack_exprs.push(syn::parse2::<syn::Expr>(quote! { context.unwrap() }).unwrap());
} }
// Handle the first argument separately if the function has a "method like" receiver // Handle the first argument separately if the function has a "method like" receiver
@ -860,13 +861,14 @@ impl ExportedFn {
#(#cfg_attrs)* #(#cfg_attrs)*
impl PluginFunction for #type_name { impl PluginFunction for #type_name {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
#(#unpack_statements)* #(#unpack_statements)*
#return_expr #return_expr
} }
#[inline(always)] fn is_method_call(&self) -> bool { #is_method_call } #[inline(always)] fn is_method_call(&self) -> bool { #is_method_call }
#[inline(always)] fn is_pure(&self) -> bool { #is_pure } #[inline(always)] fn is_pure(&self) -> bool { #is_pure }
#[inline(always)] fn has_context(&self) -> bool { #pass_context }
} }
} }
} }

View File

@ -106,6 +106,7 @@ impl Module {
impl Parse for Module { impl Parse for Module {
fn parse(input: ParseStream) -> syn::Result<Self> { fn parse(input: ParseStream) -> syn::Result<Self> {
let mut mod_all: syn::ItemMod = input.parse()?; let mut mod_all: syn::ItemMod = input.parse()?;
let fns: Vec<_>; let fns: Vec<_>;
let mut consts = Vec::new(); let mut consts = Vec::new();
let mut custom_types = Vec::new(); let mut custom_types = Vec::new();
@ -269,11 +270,17 @@ impl Module {
let (.., orig_content) = mod_all.content.take().unwrap(); let (.., orig_content) = mod_all.content.take().unwrap();
let mod_attrs = mem::take(&mut mod_all.attrs); let mod_attrs = mem::take(&mut mod_all.attrs);
#[cfg(feature = "metadata")]
let mod_doc = crate::attrs::doc_attributes(&mod_attrs)?.join("\n");
#[cfg(not(feature = "metadata"))]
let mod_doc = String::new();
if !params.skip { if !params.skip {
// Generate new module items. // Generate new module items.
// //
// This is done before inner module recursive generation, because that is destructive. // This is done before inner module recursive generation, because that is destructive.
let mod_gen = crate::rhai_module::generate_body( let mod_gen = crate::rhai_module::generate_body(
&mod_doc,
&mut fns, &mut fns,
&consts, &consts,
&custom_types, &custom_types,

View File

@ -26,6 +26,7 @@ pub struct ExportedType {
} }
pub fn generate_body( pub fn generate_body(
doc: &str,
fns: &mut [ExportedFn], fns: &mut [ExportedFn],
consts: &[ExportedConst], consts: &[ExportedConst],
custom_types: &[ExportedType], custom_types: &[ExportedType],
@ -230,7 +231,7 @@ pub fn generate_body(
syn::parse2::<syn::Stmt>(quote! { syn::parse2::<syn::Stmt>(quote! {
#(#cfg_attrs)* #(#cfg_attrs)*
m.set_fn_with_comments(#fn_literal, FnNamespace::#ns_str, FnAccess::Public, m.set_fn_with_comments(#fn_literal, FnNamespace::#ns_str, FnAccess::Public,
#param_names, &[#(#fn_input_types),*], &[#(#comments),*], #fn_token_name().into()); #param_names, [#(#fn_input_types),*], [#(#comments),*], #fn_token_name().into());
}) })
.unwrap() .unwrap()
}); });
@ -246,6 +247,17 @@ pub fn generate_body(
gen_fn_tokens.push(function.generate_impl(&fn_token_name.to_string())); gen_fn_tokens.push(function.generate_impl(&fn_token_name.to_string()));
} }
let module_docs = if doc.is_empty() {
None
} else {
Some(
syn::parse2::<syn::Stmt>(quote! {
m.set_doc(#doc);
})
.unwrap(),
)
};
let mut generate_fn_call = syn::parse2::<syn::ItemMod>(quote! { let mut generate_fn_call = syn::parse2::<syn::ItemMod>(quote! {
pub mod generate_info { pub mod generate_info {
#[allow(unused_imports)] #[allow(unused_imports)]
@ -254,6 +266,7 @@ pub fn generate_body(
#[doc(hidden)] #[doc(hidden)]
pub fn rhai_module_generate() -> Module { pub fn rhai_module_generate() -> Module {
let mut m = Module::new(); let mut m = Module::new();
#module_docs
rhai_generate_into_module(&mut m, false); rhai_generate_into_module(&mut m, false);
m.build_index(); m.build_index();
m m

View File

@ -280,12 +280,13 @@ mod generate_tests {
#[inline(always)] pub fn param_types() -> [TypeId; 0usize] { [] } #[inline(always)] pub fn param_types() -> [TypeId; 0usize] { [] }
} }
impl PluginFunction for Token { impl PluginFunction for Token {
#[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { #[inline(always)] fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
Ok(Dynamic::from(do_nothing())) Ok(Dynamic::from(do_nothing()))
} }
#[inline(always)] fn is_method_call(&self) -> bool { false } #[inline(always)] fn is_method_call(&self) -> bool { false }
#[inline(always)] fn is_pure(&self) -> bool { true } #[inline(always)] fn is_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
#[allow(unused)] #[allow(unused)]
#[doc(hidden)] #[doc(hidden)]
@ -318,13 +319,14 @@ mod generate_tests {
} }
impl PluginFunction for Token { impl PluginFunction for Token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg0 = mem::take(args[0usize]).cast::<usize>(); let arg0 = mem::take(args[0usize]).cast::<usize>();
Ok(Dynamic::from(do_something(arg0))) Ok(Dynamic::from(do_something(arg0)))
} }
#[inline(always)] fn is_method_call(&self) -> bool { false } #[inline(always)] fn is_method_call(&self) -> bool { false }
#[inline(always)] fn is_pure(&self) -> bool { true } #[inline(always)] fn is_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
#[allow(unused)] #[allow(unused)]
#[doc(hidden)] #[doc(hidden)]
@ -357,13 +359,14 @@ mod generate_tests {
} }
impl PluginFunction for Token { impl PluginFunction for Token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg0 = mem::take(args[0usize]).cast::<usize>(); let arg0 = mem::take(args[0usize]).cast::<usize>();
Ok(Dynamic::from(do_something(context, arg0))) Ok(Dynamic::from(do_something(context.unwrap(), arg0)))
} }
#[inline(always)] fn is_method_call(&self) -> bool { false } #[inline(always)] fn is_method_call(&self) -> bool { false }
#[inline(always)] fn is_pure(&self) -> bool { true } #[inline(always)] fn is_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { true }
} }
#[allow(unused)] #[allow(unused)]
#[doc(hidden)] #[doc(hidden)]
@ -399,12 +402,13 @@ mod generate_tests {
} }
impl PluginFunction for Token { impl PluginFunction for Token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
Ok(Dynamic::from(return_dynamic())) Ok(Dynamic::from(return_dynamic()))
} }
#[inline(always)] fn is_method_call(&self) -> bool { false } #[inline(always)] fn is_method_call(&self) -> bool { false }
#[inline(always)] fn is_pure(&self) -> bool { true } #[inline(always)] fn is_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
#[allow(unused)] #[allow(unused)]
#[doc(hidden)] #[doc(hidden)]
@ -432,13 +436,14 @@ mod generate_tests {
} }
impl PluginFunction for TestStruct { impl PluginFunction for TestStruct {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg0 = mem::take(args[0usize]).cast::<usize>(); let arg0 = mem::take(args[0usize]).cast::<usize>();
Ok(Dynamic::from(do_something(arg0))) Ok(Dynamic::from(do_something(arg0)))
} }
#[inline(always)] fn is_method_call(&self) -> bool { false } #[inline(always)] fn is_method_call(&self) -> bool { false }
#[inline(always)] fn is_pure(&self) -> bool { true } #[inline(always)] fn is_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
}; };
@ -465,7 +470,7 @@ mod generate_tests {
} }
impl PluginFunction for Token { impl PluginFunction for Token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg0 = mem::take(args[0usize]).cast::<usize>(); let arg0 = mem::take(args[0usize]).cast::<usize>();
let arg1 = mem::take(args[1usize]).cast::<usize>(); let arg1 = mem::take(args[1usize]).cast::<usize>();
Ok(Dynamic::from(add_together(arg0, arg1))) Ok(Dynamic::from(add_together(arg0, arg1)))
@ -473,6 +478,7 @@ mod generate_tests {
#[inline(always)] fn is_method_call(&self) -> bool { false } #[inline(always)] fn is_method_call(&self) -> bool { false }
#[inline(always)] fn is_pure(&self) -> bool { true } #[inline(always)] fn is_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
#[allow(unused)] #[allow(unused)]
#[doc(hidden)] #[doc(hidden)]
@ -505,7 +511,7 @@ mod generate_tests {
} }
impl PluginFunction for Token { impl PluginFunction for Token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg1 = mem::take(args[1usize]).cast::<usize>(); let arg1 = mem::take(args[1usize]).cast::<usize>();
let arg0 = &mut args[0usize].write_lock::<usize>().unwrap(); let arg0 = &mut args[0usize].write_lock::<usize>().unwrap();
Ok(Dynamic::from(increment(arg0, arg1))) Ok(Dynamic::from(increment(arg0, arg1)))
@ -513,6 +519,7 @@ mod generate_tests {
#[inline(always)] fn is_method_call(&self) -> bool { true } #[inline(always)] fn is_method_call(&self) -> bool { true }
#[inline(always)] fn is_pure(&self) -> bool { false } #[inline(always)] fn is_pure(&self) -> bool { false }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
#[allow(unused)] #[allow(unused)]
#[doc(hidden)] #[doc(hidden)]
@ -546,13 +553,14 @@ mod generate_tests {
} }
impl PluginFunction for Token { impl PluginFunction for Token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg0 = mem::take(args[0usize]).into_immutable_string().unwrap(); let arg0 = mem::take(args[0usize]).into_immutable_string().unwrap();
Ok(Dynamic::from(special_print(&arg0))) Ok(Dynamic::from(special_print(&arg0)))
} }
#[inline(always)] fn is_method_call(&self) -> bool { false } #[inline(always)] fn is_method_call(&self) -> bool { false }
#[inline(always)] fn is_pure(&self) -> bool { true } #[inline(always)] fn is_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
#[allow(unused)] #[allow(unused)]
#[doc(hidden)] #[doc(hidden)]

View File

@ -92,10 +92,11 @@ mod module_tests {
.cloned() .cloned()
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
vec![ vec![
"/// This is a doc-comment.", "\
"/// Another line.", /// This is a doc-comment.\n\
"/// block doc-comment ", /// Another line.\n\
"/// Final line.", /// block doc-comment \n\
/// Final line.",
"/** doc-comment\n in multiple lines\n */" "/** doc-comment\n in multiple lines\n */"
] ]
); );
@ -385,12 +386,13 @@ mod generate_tests {
} }
impl PluginFunction for get_mystic_number_token { impl PluginFunction for get_mystic_number_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
Ok(Dynamic::from(get_mystic_number())) Ok(Dynamic::from(get_mystic_number()))
} }
#[inline(always)] fn is_method_call(&self) -> bool { false } #[inline(always)] fn is_method_call(&self) -> bool { false }
#[inline(always)] fn is_pure(&self) -> bool { true } #[inline(always)] fn is_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
} }
}; };
@ -402,6 +404,12 @@ mod generate_tests {
#[test] #[test]
fn one_factory_fn_with_comments_module() { fn one_factory_fn_with_comments_module() {
let input_tokens: TokenStream = quote! { let input_tokens: TokenStream = quote! {
/// This is the one_fn module!
/** block doc-comment
* multi-line
*/
/// Another line!
/// Final line!
pub mod one_fn { pub mod one_fn {
/// This is a doc-comment. /// This is a doc-comment.
/// Another line. /// Another line.
@ -418,6 +426,12 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
/// This is the one_fn module!
/** block doc-comment
* multi-line
*/
/// Another line!
/// Final line!
pub mod one_fn { pub mod one_fn {
/// This is a doc-comment. /// This is a doc-comment.
/// Another line. /// Another line.
@ -436,6 +450,7 @@ mod generate_tests {
#[doc(hidden)] #[doc(hidden)]
pub fn rhai_module_generate() -> Module { pub fn rhai_module_generate() -> Module {
let mut m = Module::new(); let mut m = Module::new();
m.set_doc("/// This is the one_fn module!\n/** block doc-comment\n * multi-line\n */\n/// Another line!\n/// Final line!");
rhai_generate_into_module(&mut m, false); rhai_generate_into_module(&mut m, false);
m.build_index(); m.build_index();
m m
@ -444,11 +459,8 @@ mod generate_tests {
#[doc(hidden)] #[doc(hidden)]
pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) {
m.set_fn_with_comments("get_mystic_number", FnNamespace::Internal, FnAccess::Public, m.set_fn_with_comments("get_mystic_number", FnNamespace::Internal, FnAccess::Public,
Some(get_mystic_number_token::PARAM_NAMES), &[], &[ Some(get_mystic_number_token::PARAM_NAMES), [], [
"/// This is a doc-comment.", "/// This is a doc-comment.\n/// Another line.\n/// block doc-comment \n/// Final line.",
"/// Another line.",
"/// block doc-comment ",
"/// Final line.",
"/** doc-comment\n in multiple lines\n */" "/** doc-comment\n in multiple lines\n */"
], get_mystic_number_token().into()); ], get_mystic_number_token().into());
if flatten {} else {} if flatten {} else {}
@ -463,12 +475,13 @@ mod generate_tests {
} }
impl PluginFunction for get_mystic_number_token { impl PluginFunction for get_mystic_number_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
Ok(Dynamic::from(get_mystic_number())) Ok(Dynamic::from(get_mystic_number()))
} }
#[inline(always)] fn is_method_call(&self) -> bool { false } #[inline(always)] fn is_method_call(&self) -> bool { false }
#[inline(always)] fn is_pure(&self) -> bool { true } #[inline(always)] fn is_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
} }
}; };
@ -521,13 +534,14 @@ mod generate_tests {
} }
impl PluginFunction for add_one_to_token { impl PluginFunction for add_one_to_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg0 = mem::take(args[0usize]).cast::<INT>(); let arg0 = mem::take(args[0usize]).cast::<INT>();
Ok(Dynamic::from(add_one_to(arg0))) Ok(Dynamic::from(add_one_to(arg0)))
} }
#[inline(always)] fn is_method_call(&self) -> bool { false } #[inline(always)] fn is_method_call(&self) -> bool { false }
#[inline(always)] fn is_pure(&self) -> bool { true } #[inline(always)] fn is_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
} }
}; };
@ -579,13 +593,14 @@ mod generate_tests {
} }
impl PluginFunction for add_one_to_token { impl PluginFunction for add_one_to_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg0 = mem::take(args[0usize]).cast::<INT>(); let arg0 = mem::take(args[0usize]).cast::<INT>();
Ok(Dynamic::from(add_one_to(arg0))) Ok(Dynamic::from(add_one_to(arg0)))
} }
#[inline(always)] fn is_method_call(&self) -> bool { false } #[inline(always)] fn is_method_call(&self) -> bool { false }
#[inline(always)] fn is_pure(&self) -> bool { true } #[inline(always)] fn is_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
} }
}; };
@ -651,13 +666,14 @@ mod generate_tests {
} }
impl PluginFunction for add_one_to_token { impl PluginFunction for add_one_to_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg0 = mem::take(args[0usize]).cast::<INT>(); let arg0 = mem::take(args[0usize]).cast::<INT>();
Ok(Dynamic::from(add_one_to(arg0))) Ok(Dynamic::from(add_one_to(arg0)))
} }
#[inline(always)] fn is_method_call(&self) -> bool { false } #[inline(always)] fn is_method_call(&self) -> bool { false }
#[inline(always)] fn is_pure(&self) -> bool { true } #[inline(always)] fn is_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
@ -670,7 +686,7 @@ mod generate_tests {
} }
impl PluginFunction for add_n_to_token { impl PluginFunction for add_n_to_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg0 = mem::take(args[0usize]).cast::<INT>(); let arg0 = mem::take(args[0usize]).cast::<INT>();
let arg1 = mem::take(args[1usize]).cast::<INT>(); let arg1 = mem::take(args[1usize]).cast::<INT>();
Ok(Dynamic::from(add_n_to(arg0, arg1))) Ok(Dynamic::from(add_n_to(arg0, arg1)))
@ -678,6 +694,7 @@ mod generate_tests {
#[inline(always)] fn is_method_call(&self) -> bool { false } #[inline(always)] fn is_method_call(&self) -> bool { false }
#[inline(always)] fn is_pure(&self) -> bool { true } #[inline(always)] fn is_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
} }
}; };
@ -729,7 +746,7 @@ mod generate_tests {
} }
impl PluginFunction for add_together_token { impl PluginFunction for add_together_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg0 = mem::take(args[0usize]).cast::<INT>(); let arg0 = mem::take(args[0usize]).cast::<INT>();
let arg1 = mem::take(args[1usize]).cast::<INT>(); let arg1 = mem::take(args[1usize]).cast::<INT>();
Ok(Dynamic::from(add_together(arg0, arg1))) Ok(Dynamic::from(add_together(arg0, arg1)))
@ -737,6 +754,7 @@ mod generate_tests {
#[inline(always)] fn is_method_call(&self) -> bool { false } #[inline(always)] fn is_method_call(&self) -> bool { false }
#[inline(always)] fn is_pure(&self) -> bool { true } #[inline(always)] fn is_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
} }
}; };
@ -795,7 +813,7 @@ mod generate_tests {
} }
impl PluginFunction for add_together_token { impl PluginFunction for add_together_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg0 = mem::take(args[0usize]).cast::<INT>(); let arg0 = mem::take(args[0usize]).cast::<INT>();
let arg1 = mem::take(args[1usize]).cast::<INT>(); let arg1 = mem::take(args[1usize]).cast::<INT>();
Ok(Dynamic::from(add_together(arg0, arg1))) Ok(Dynamic::from(add_together(arg0, arg1)))
@ -803,6 +821,7 @@ mod generate_tests {
#[inline(always)] fn is_method_call(&self) -> bool { false } #[inline(always)] fn is_method_call(&self) -> bool { false }
#[inline(always)] fn is_pure(&self) -> bool { true } #[inline(always)] fn is_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
} }
}; };
@ -871,13 +890,14 @@ mod generate_tests {
} }
impl PluginFunction for get_mystic_number_token { impl PluginFunction for get_mystic_number_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg0 = &mut args[0usize].write_lock::<Hello>().unwrap(); let arg0 = &mut args[0usize].write_lock::<Hello>().unwrap();
Ok(Dynamic::from(get_mystic_number(arg0))) Ok(Dynamic::from(get_mystic_number(arg0)))
} }
#[inline(always)] fn is_method_call(&self) -> bool { true } #[inline(always)] fn is_method_call(&self) -> bool { true }
#[inline(always)] fn is_pure(&self) -> bool { false } #[inline(always)] fn is_pure(&self) -> bool { false }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
} }
}; };
@ -1081,12 +1101,13 @@ mod generate_tests {
} }
impl PluginFunction for get_mystic_number_token { impl PluginFunction for get_mystic_number_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
Ok(Dynamic::from(get_mystic_number())) Ok(Dynamic::from(get_mystic_number()))
} }
#[inline(always)] fn is_method_call(&self) -> bool { false } #[inline(always)] fn is_method_call(&self) -> bool { false }
#[inline(always)] fn is_pure(&self) -> bool { true } #[inline(always)] fn is_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
} }
}; };
@ -1171,13 +1192,14 @@ mod generate_tests {
} }
impl PluginFunction for print_out_to_token { impl PluginFunction for print_out_to_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg0 = mem::take(args[0usize]).into_immutable_string().unwrap(); let arg0 = mem::take(args[0usize]).into_immutable_string().unwrap();
Ok(Dynamic::from(print_out_to(&arg0))) Ok(Dynamic::from(print_out_to(&arg0)))
} }
#[inline(always)] fn is_method_call(&self) -> bool { false } #[inline(always)] fn is_method_call(&self) -> bool { false }
#[inline(always)] fn is_pure(&self) -> bool { true } #[inline(always)] fn is_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
} }
}; };
@ -1229,13 +1251,14 @@ mod generate_tests {
} }
impl PluginFunction for print_out_to_token { impl PluginFunction for print_out_to_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg0 = mem::take(args[0usize]).into_string().unwrap(); let arg0 = mem::take(args[0usize]).into_string().unwrap();
Ok(Dynamic::from(print_out_to(arg0))) Ok(Dynamic::from(print_out_to(arg0)))
} }
#[inline(always)] fn is_method_call(&self) -> bool { false } #[inline(always)] fn is_method_call(&self) -> bool { false }
#[inline(always)] fn is_pure(&self) -> bool { true } #[inline(always)] fn is_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
} }
}; };
@ -1288,7 +1311,7 @@ mod generate_tests {
} }
impl PluginFunction for foo_token { impl PluginFunction for foo_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg1 = mem::take(args[1usize]).cast::<INT>(); let arg1 = mem::take(args[1usize]).cast::<INT>();
let arg0 = &mut args[0usize].write_lock::<FLOAT>().unwrap(); let arg0 = &mut args[0usize].write_lock::<FLOAT>().unwrap();
Ok(Dynamic::from(foo(arg0, arg1))) Ok(Dynamic::from(foo(arg0, arg1)))
@ -1296,6 +1319,7 @@ mod generate_tests {
#[inline(always)] fn is_method_call(&self) -> bool { true } #[inline(always)] fn is_method_call(&self) -> bool { true }
#[inline(always)] fn is_pure(&self) -> bool { true } #[inline(always)] fn is_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
} }
}; };
@ -1347,13 +1371,14 @@ mod generate_tests {
} }
impl PluginFunction for increment_token { impl PluginFunction for increment_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg0 = &mut args[0usize].write_lock::<FLOAT>().unwrap(); let arg0 = &mut args[0usize].write_lock::<FLOAT>().unwrap();
Ok(Dynamic::from(increment(arg0))) Ok(Dynamic::from(increment(arg0)))
} }
#[inline(always)] fn is_method_call(&self) -> bool { true } #[inline(always)] fn is_method_call(&self) -> bool { true }
#[inline(always)] fn is_pure(&self) -> bool { false } #[inline(always)] fn is_pure(&self) -> bool { false }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
} }
}; };
@ -1408,13 +1433,14 @@ mod generate_tests {
} }
impl PluginFunction for increment_token { impl PluginFunction for increment_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg0 = &mut args[0usize].write_lock::<FLOAT>().unwrap(); let arg0 = &mut args[0usize].write_lock::<FLOAT>().unwrap();
Ok(Dynamic::from(increment(arg0))) Ok(Dynamic::from(increment(arg0)))
} }
#[inline(always)] fn is_method_call(&self) -> bool { true } #[inline(always)] fn is_method_call(&self) -> bool { true }
#[inline(always)] fn is_pure(&self) -> bool { false } #[inline(always)] fn is_pure(&self) -> bool { false }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
} }
#[allow(unused_imports)] #[allow(unused_imports)]
@ -1492,13 +1518,14 @@ mod generate_tests {
} }
impl PluginFunction for increment_token { impl PluginFunction for increment_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg0 = &mut args[0usize].write_lock::<FLOAT>().unwrap(); let arg0 = &mut args[0usize].write_lock::<FLOAT>().unwrap();
Ok(Dynamic::from(increment(arg0))) Ok(Dynamic::from(increment(arg0)))
} }
#[inline(always)] fn is_method_call(&self) -> bool { true } #[inline(always)] fn is_method_call(&self) -> bool { true }
#[inline(always)] fn is_pure(&self) -> bool { false } #[inline(always)] fn is_pure(&self) -> bool { false }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
} }
#[allow(unused_imports)] #[allow(unused_imports)]
@ -1577,13 +1604,14 @@ mod generate_tests {
} }
impl PluginFunction for int_foo_token { impl PluginFunction for int_foo_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg0 = &mut args[0usize].write_lock::<u64>().unwrap(); let arg0 = &mut args[0usize].write_lock::<u64>().unwrap();
Ok(Dynamic::from(int_foo(arg0))) Ok(Dynamic::from(int_foo(arg0)))
} }
#[inline(always)] fn is_method_call(&self) -> bool { true } #[inline(always)] fn is_method_call(&self) -> bool { true }
#[inline(always)] fn is_pure(&self) -> bool { false } #[inline(always)] fn is_pure(&self) -> bool { false }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
} }
}; };
@ -1639,13 +1667,14 @@ mod generate_tests {
} }
impl PluginFunction for int_foo_token { impl PluginFunction for int_foo_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg0 = &mut args[0usize].write_lock::<u64>().unwrap(); let arg0 = &mut args[0usize].write_lock::<u64>().unwrap();
Ok(Dynamic::from(int_foo(arg0))) Ok(Dynamic::from(int_foo(arg0)))
} }
#[inline(always)] fn is_method_call(&self) -> bool { true } #[inline(always)] fn is_method_call(&self) -> bool { true }
#[inline(always)] fn is_pure(&self) -> bool { false } #[inline(always)] fn is_pure(&self) -> bool { false }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
} }
}; };
@ -1698,7 +1727,7 @@ mod generate_tests {
} }
impl PluginFunction for int_foo_token { impl PluginFunction for int_foo_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg1 = mem::take(args[1usize]).cast::<u64>(); let arg1 = mem::take(args[1usize]).cast::<u64>();
let arg0 = &mut args[0usize].write_lock::<u64>().unwrap(); let arg0 = &mut args[0usize].write_lock::<u64>().unwrap();
Ok(Dynamic::from(int_foo(arg0, arg1))) Ok(Dynamic::from(int_foo(arg0, arg1)))
@ -1706,6 +1735,7 @@ mod generate_tests {
#[inline(always)] fn is_method_call(&self) -> bool { true } #[inline(always)] fn is_method_call(&self) -> bool { true }
#[inline(always)] fn is_pure(&self) -> bool { false } #[inline(always)] fn is_pure(&self) -> bool { false }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
} }
}; };
@ -1761,7 +1791,7 @@ mod generate_tests {
} }
impl PluginFunction for int_foo_token { impl PluginFunction for int_foo_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg1 = mem::take(args[1usize]).cast::<u64>(); let arg1 = mem::take(args[1usize]).cast::<u64>();
let arg0 = &mut args[0usize].write_lock::<u64>().unwrap(); let arg0 = &mut args[0usize].write_lock::<u64>().unwrap();
Ok(Dynamic::from(int_foo(arg0, arg1))) Ok(Dynamic::from(int_foo(arg0, arg1)))
@ -1769,6 +1799,7 @@ mod generate_tests {
#[inline(always)] fn is_method_call(&self) -> bool { true } #[inline(always)] fn is_method_call(&self) -> bool { true }
#[inline(always)] fn is_pure(&self) -> bool { false } #[inline(always)] fn is_pure(&self) -> bool { false }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
} }
}; };
@ -1821,7 +1852,7 @@ mod generate_tests {
} }
impl PluginFunction for get_by_index_token { impl PluginFunction for get_by_index_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg1 = mem::take(args[1usize]).cast::<u64>(); let arg1 = mem::take(args[1usize]).cast::<u64>();
let arg0 = &mut args[0usize].write_lock::<MyCollection>().unwrap(); let arg0 = &mut args[0usize].write_lock::<MyCollection>().unwrap();
Ok(Dynamic::from(get_by_index(arg0, arg1))) Ok(Dynamic::from(get_by_index(arg0, arg1)))
@ -1829,6 +1860,7 @@ mod generate_tests {
#[inline(always)] fn is_method_call(&self) -> bool { true } #[inline(always)] fn is_method_call(&self) -> bool { true }
#[inline(always)] fn is_pure(&self) -> bool { false } #[inline(always)] fn is_pure(&self) -> bool { false }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
} }
}; };
@ -1889,7 +1921,7 @@ mod generate_tests {
#[cfg(hello)] #[cfg(hello)]
impl PluginFunction for get_by_index_token { impl PluginFunction for get_by_index_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg1 = mem::take(args[1usize]).cast::<u64>(); let arg1 = mem::take(args[1usize]).cast::<u64>();
let arg0 = &mut args[0usize].write_lock::<MyCollection>().unwrap(); let arg0 = &mut args[0usize].write_lock::<MyCollection>().unwrap();
Ok(Dynamic::from(get_by_index(arg0, arg1))) Ok(Dynamic::from(get_by_index(arg0, arg1)))
@ -1897,6 +1929,7 @@ mod generate_tests {
#[inline(always)] fn is_method_call(&self) -> bool { true } #[inline(always)] fn is_method_call(&self) -> bool { true }
#[inline(always)] fn is_pure(&self) -> bool { false } #[inline(always)] fn is_pure(&self) -> bool { false }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
} }
}; };
@ -1952,7 +1985,7 @@ mod generate_tests {
} }
impl PluginFunction for get_by_index_token { impl PluginFunction for get_by_index_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg1 = mem::take(args[1usize]).cast::<u64>(); let arg1 = mem::take(args[1usize]).cast::<u64>();
let arg0 = &mut args[0usize].write_lock::<MyCollection>().unwrap(); let arg0 = &mut args[0usize].write_lock::<MyCollection>().unwrap();
Ok(Dynamic::from(get_by_index(arg0, arg1))) Ok(Dynamic::from(get_by_index(arg0, arg1)))
@ -1960,6 +1993,7 @@ mod generate_tests {
#[inline(always)] fn is_method_call(&self) -> bool { true } #[inline(always)] fn is_method_call(&self) -> bool { true }
#[inline(always)] fn is_pure(&self) -> bool { false } #[inline(always)] fn is_pure(&self) -> bool { false }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
} }
}; };
@ -2012,7 +2046,7 @@ mod generate_tests {
} }
impl PluginFunction for set_by_index_token { impl PluginFunction for set_by_index_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg1 = mem::take(args[1usize]).cast::<u64>(); let arg1 = mem::take(args[1usize]).cast::<u64>();
let arg2 = mem::take(args[2usize]).cast::<FLOAT>(); let arg2 = mem::take(args[2usize]).cast::<FLOAT>();
let arg0 = &mut args[0usize].write_lock::<MyCollection>().unwrap(); let arg0 = &mut args[0usize].write_lock::<MyCollection>().unwrap();
@ -2021,6 +2055,7 @@ mod generate_tests {
#[inline(always)] fn is_method_call(&self) -> bool { true } #[inline(always)] fn is_method_call(&self) -> bool { true }
#[inline(always)] fn is_pure(&self) -> bool { false } #[inline(always)] fn is_pure(&self) -> bool { false }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
} }
}; };
@ -2076,7 +2111,7 @@ mod generate_tests {
} }
impl PluginFunction for set_by_index_token { impl PluginFunction for set_by_index_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: Option<NativeCallContext>, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg1 = mem::take(args[1usize]).cast::<u64>(); let arg1 = mem::take(args[1usize]).cast::<u64>();
let arg2 = mem::take(args[2usize]).cast::<FLOAT>(); let arg2 = mem::take(args[2usize]).cast::<FLOAT>();
let arg0 = &mut args[0usize].write_lock::<MyCollection>().unwrap(); let arg0 = &mut args[0usize].write_lock::<MyCollection>().unwrap();
@ -2085,6 +2120,7 @@ mod generate_tests {
#[inline(always)] fn is_method_call(&self) -> bool { true } #[inline(always)] fn is_method_call(&self) -> bool { true }
#[inline(always)] fn is_pure(&self) -> bool { false } #[inline(always)] fn is_pure(&self) -> bool { false }
#[inline(always)] fn has_context(&self) -> bool { false }
} }
} }
}; };

View File

@ -4,7 +4,7 @@
fn init() { fn init() {
// Can detect system-provided default states! // Can detect system-provided default states!
// Add 'bool_state' as new state variable if one does not exist // Add 'bool_state' as new state variable if one does not exist
if !("bool_state" in this) { if "bool_state" !in this {
this.bool_state = false; this.bool_state = false;
} }
// Add 'value' as new state variable (overwrites any existing) // Add 'value' as new state variable (overwrites any existing)

View File

@ -4,7 +4,7 @@
/// State is stored inside an object map bound to 'state'. /// State is stored inside an object map bound to 'state'.
fn init() { fn init() {
// Add 'bool_state' as new state variable if one does not exist // Add 'bool_state' as new state variable if one does not exist
if !("bool_state" in state) { if "bool_state" !in state {
state.bool_state = false; state.bool_state = false;
} }
// Add 'obj_state' as new state variable (overwrites any existing) // Add 'obj_state' as new state variable (overwrites any existing)
@ -37,7 +37,7 @@ fn start(data) {
/// 'end' event handler /// 'end' event handler
fn end(data) { fn end(data) {
if !state.bool_state || !("start_mode" in state) { if !state.bool_state || "start_mode" !in state {
throw "Not yet started!"; throw "Not yet started!";
} }
if state.value > 0 { if state.value > 0 {

View File

@ -88,6 +88,7 @@ impl Engine {
/// ///
/// To define a pretty-print name, call [`with_name`][`TypeBuilder::with_name`], /// To define a pretty-print name, call [`with_name`][`TypeBuilder::with_name`],
/// to use [`Engine::register_type_with_name`] instead. /// to use [`Engine::register_type_with_name`] instead.
#[must_use]
pub struct TypeBuilder<'a, T: Variant + Clone> { pub struct TypeBuilder<'a, T: Variant + Clone> {
engine: &'a mut Engine, engine: &'a mut Engine,
name: Option<&'static str>, name: Option<&'static str>,
@ -101,7 +102,7 @@ impl<'a, T: Variant + Clone> TypeBuilder<'a, T> {
Self { Self {
engine, engine,
name: None, name: None,
_marker: PhantomData::default(), _marker: PhantomData,
} }
} }
} }
@ -116,10 +117,10 @@ impl<'a, T: Variant + Clone> TypeBuilder<'a, T> {
/// Register a custom function. /// Register a custom function.
#[inline(always)] #[inline(always)]
pub fn with_fn<A, R, S>( pub fn with_fn<A: 'static, const N: usize, const C: bool, R: Variant + Clone, const L: bool>(
&mut self, &mut self,
name: impl AsRef<str> + Into<Identifier>, name: impl AsRef<str> + Into<Identifier>,
method: impl RegisterNativeFunction<A, R, S>, method: impl RegisterNativeFunction<A, N, C, R, L>,
) -> &mut Self { ) -> &mut Self {
self.engine.register_fn(name, method); self.engine.register_fn(name, method);
self self
@ -148,10 +149,10 @@ impl<'a, T: Variant + Clone> TypeBuilder<'a, T> {
/// ///
/// Not available under `no_object`. /// Not available under `no_object`.
#[inline(always)] #[inline(always)]
pub fn with_get<V: Variant + Clone, S>( pub fn with_get<const C: bool, V: Variant + Clone, const L: bool>(
&mut self, &mut self,
name: impl AsRef<str>, name: impl AsRef<str>,
get_fn: impl RegisterNativeFunction<(Mut<T>,), V, S> + crate::func::SendSync + 'static, get_fn: impl RegisterNativeFunction<(Mut<T>,), 1, C, V, L> + crate::func::SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
self.engine.register_get(name, get_fn); self.engine.register_get(name, get_fn);
self self
@ -161,10 +162,10 @@ impl<'a, T: Variant + Clone> TypeBuilder<'a, T> {
/// ///
/// Not available under `no_object`. /// Not available under `no_object`.
#[inline(always)] #[inline(always)]
pub fn with_set<V: Variant + Clone, S>( pub fn with_set<const C: bool, V: Variant + Clone, const L: bool>(
&mut self, &mut self,
name: impl AsRef<str>, name: impl AsRef<str>,
set_fn: impl RegisterNativeFunction<(Mut<T>, V), (), S> + crate::func::SendSync + 'static, set_fn: impl RegisterNativeFunction<(Mut<T>, V), 2, C, (), L> + crate::func::SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
self.engine.register_set(name, set_fn); self.engine.register_set(name, set_fn);
self self
@ -176,11 +177,19 @@ impl<'a, T: Variant + Clone> TypeBuilder<'a, T> {
/// ///
/// Not available under `no_object`. /// Not available under `no_object`.
#[inline(always)] #[inline(always)]
pub fn with_get_set<V: Variant + Clone, S1, S2>( pub fn with_get_set<
const C1: bool,
const C2: bool,
V: Variant + Clone,
const L1: bool,
const L2: bool,
>(
&mut self, &mut self,
name: impl AsRef<str>, name: impl AsRef<str>,
get_fn: impl RegisterNativeFunction<(Mut<T>,), V, S1> + crate::func::SendSync + 'static, get_fn: impl RegisterNativeFunction<(Mut<T>,), 1, C1, V, L1> + crate::func::SendSync + 'static,
set_fn: impl RegisterNativeFunction<(Mut<T>, V), (), S2> + crate::func::SendSync + 'static, set_fn: impl RegisterNativeFunction<(Mut<T>, V), 2, C2, (), L2>
+ crate::func::SendSync
+ 'static,
) -> &mut Self { ) -> &mut Self {
self.engine.register_get_set(name, get_fn, set_fn); self.engine.register_get_set(name, get_fn, set_fn);
self self
@ -195,9 +204,14 @@ impl<'a, T: Variant + Clone> TypeBuilder<'a, T> {
/// ///
/// Not available under both `no_index` and `no_object`. /// Not available under both `no_index` and `no_object`.
#[inline(always)] #[inline(always)]
pub fn with_indexer_get<X: Variant + Clone, V: Variant + Clone, S>( pub fn with_indexer_get<
X: Variant + Clone,
const C: bool,
V: Variant + Clone,
const L: bool,
>(
&mut self, &mut self,
get_fn: impl RegisterNativeFunction<(Mut<T>, X), V, S> + crate::func::SendSync + 'static, get_fn: impl RegisterNativeFunction<(Mut<T>, X), 2, C, V, L> + crate::func::SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
self.engine.register_indexer_get(get_fn); self.engine.register_indexer_get(get_fn);
self self
@ -207,9 +221,16 @@ impl<'a, T: Variant + Clone> TypeBuilder<'a, T> {
/// ///
/// Not available under both `no_index` and `no_object`. /// Not available under both `no_index` and `no_object`.
#[inline(always)] #[inline(always)]
pub fn with_indexer_set<X: Variant + Clone, V: Variant + Clone, S>( pub fn with_indexer_set<
X: Variant + Clone,
const C: bool,
V: Variant + Clone,
const L: bool,
>(
&mut self, &mut self,
set_fn: impl RegisterNativeFunction<(Mut<T>, X, V), (), S> + crate::func::SendSync + 'static, set_fn: impl RegisterNativeFunction<(Mut<T>, X, V), 3, C, (), L>
+ crate::func::SendSync
+ 'static,
) -> &mut Self { ) -> &mut Self {
self.engine.register_indexer_set(set_fn); self.engine.register_indexer_set(set_fn);
self self
@ -219,10 +240,19 @@ impl<'a, T: Variant + Clone> TypeBuilder<'a, T> {
/// ///
/// Not available under both `no_index` and `no_object`. /// Not available under both `no_index` and `no_object`.
#[inline(always)] #[inline(always)]
pub fn with_indexer_get_set<X: Variant + Clone, V: Variant + Clone, S1, S2>( pub fn with_indexer_get_set<
X: Variant + Clone,
const C1: bool,
const C2: bool,
V: Variant + Clone,
const L1: bool,
const L2: bool,
>(
&mut self, &mut self,
get_fn: impl RegisterNativeFunction<(Mut<T>, X), V, S1> + crate::func::SendSync + 'static, get_fn: impl RegisterNativeFunction<(Mut<T>, X), 2, C1, V, L1> + crate::func::SendSync + 'static,
set_fn: impl RegisterNativeFunction<(Mut<T>, X, V), (), S2> + crate::func::SendSync + 'static, set_fn: impl RegisterNativeFunction<(Mut<T>, X, V), 3, C2, (), L2>
+ crate::func::SendSync
+ 'static,
) -> &mut Self { ) -> &mut Self {
self.engine.register_indexer_get_set(get_fn, set_fn); self.engine.register_indexer_get_set(get_fn, set_fn);
self self

View File

@ -3,10 +3,8 @@
use crate::eval::{Caches, GlobalRuntimeState}; use crate::eval::{Caches, GlobalRuntimeState};
use crate::types::dynamic::Variant; use crate::types::dynamic::Variant;
use crate::types::RestoreOnDrop;
use crate::{ use crate::{
reify, Dynamic, Engine, FuncArgs, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, AST, Dynamic, Engine, FuncArgs, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, AST, ERR,
ERR,
}; };
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -81,7 +79,7 @@ impl Engine {
/// ///
/// The [`AST`] is evaluated before calling the function. /// The [`AST`] is evaluated before calling the function.
/// This allows a script to load the necessary modules. /// This allows a script to load the necessary modules.
/// This is usually desired. If not, use [`call_fn_with_options`] instead. /// This is usually desired. If not, use [`call_fn_with_options`][Engine::call_fn_with_options] instead.
/// ///
/// # Example /// # Example
/// ///
@ -240,10 +238,10 @@ impl Engine {
let rewind_scope = options.rewind_scope; let rewind_scope = options.rewind_scope;
let result = if options.eval_ast && !statements.is_empty() { let result = if options.eval_ast && !statements.is_empty() {
let orig_scope_len = scope.len(); auto_restore! {
let scope = &mut *RestoreOnDrop::lock_if(rewind_scope, scope, move |s| { scope if rewind_scope => rewind;
s.rewind(orig_scope_len); let orig_scope_len = scope.len();
}); }
self.eval_global_statements(global, caches, scope, statements) self.eval_global_statements(global, caches, scope, statements)
} else { } else {
@ -254,7 +252,7 @@ impl Engine {
// Check for data race. // Check for data race.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
crate::func::ensure_no_data_race(name, args, false).map(|_| Dynamic::UNIT)?; crate::func::ensure_no_data_race(name, args, false)?;
ast.shared_lib() ast.shared_lib()
.get_script_fn(name, args.len()) .get_script_fn(name, args.len())
@ -276,7 +274,7 @@ impl Engine {
}); });
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
if self.debugger.is_some() { if self.is_debugger_registered() {
global.debugger_mut().status = crate::eval::DebuggerStatus::Terminate; global.debugger_mut().status = crate::eval::DebuggerStatus::Terminate;
let node = &crate::ast::Stmt::Noop(Position::NONE); let node = &crate::ast::Stmt::Noop(Position::NONE);
self.run_debugger(global, caches, scope, this_ptr, node)?; self.run_debugger(global, caches, scope, this_ptr, node)?;

View File

@ -218,15 +218,12 @@ impl Engine {
scripts: impl AsRef<[S]>, scripts: impl AsRef<[S]>,
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> ParseResult<AST> { ) -> ParseResult<AST> {
let (stream, tokenizer_control) = self.lex_raw( let (stream, tc) = self.lex_raw(scripts.as_ref(), self.token_mapper.as_deref());
scripts.as_ref(),
self.token_mapper.as_ref().map(<_>::as_ref),
);
let interned_strings = &mut *locked_write(&self.interned_strings); let interned_strings = &mut *locked_write(&self.interned_strings);
let mut state = ParseState::new(scope, interned_strings, tokenizer_control); let state = &mut ParseState::new(scope, interned_strings, tc);
let mut _ast = self.parse(&mut stream.peekable(), &mut state, optimization_level)?; let mut _ast = self.parse(stream.peekable(), state, optimization_level)?;
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
_ast.set_doc(state.tokenizer_control.borrow().global_comments.join("\n")); _ast.set_doc(&state.tokenizer_control.borrow().global_comments);
Ok(_ast) Ok(_ast)
} }
/// Compile a string containing an expression into an [`AST`], /// Compile a string containing an expression into an [`AST`],
@ -292,12 +289,9 @@ impl Engine {
script: impl AsRef<str>, script: impl AsRef<str>,
) -> ParseResult<AST> { ) -> ParseResult<AST> {
let scripts = [script]; let scripts = [script];
let (stream, tokenizer_control) = let (stream, t) = self.lex_raw(&scripts, self.token_mapper.as_deref());
self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref));
let mut peekable = stream.peekable();
let interned_strings = &mut *locked_write(&self.interned_strings); let interned_strings = &mut *locked_write(&self.interned_strings);
let mut state = ParseState::new(scope, interned_strings, tokenizer_control); let state = &mut ParseState::new(scope, interned_strings, t);
self.parse_global_expr(&mut peekable, &mut state, |_| {}, self.optimization_level) self.parse_global_expr(stream.peekable(), state, |_| {}, self.optimization_level)
} }
} }

View File

@ -4,11 +4,11 @@
use crate::ast::Expr; use crate::ast::Expr;
use crate::func::SendSync; use crate::func::SendSync;
use crate::parser::ParseResult; use crate::parser::ParseResult;
use crate::tokenizer::{is_valid_identifier, Token}; use crate::tokenizer::{is_valid_identifier, Token, NO_TOKEN};
use crate::types::dynamic::Variant; use crate::types::dynamic::Variant;
use crate::{ use crate::{
reify, Dynamic, Engine, EvalContext, Identifier, ImmutableString, LexError, Position, Dynamic, Engine, EvalContext, Identifier, ImmutableString, LexError, Position, RhaiResult,
RhaiResult, StaticVec, StaticVec,
}; };
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -166,6 +166,7 @@ impl Deref for Expression<'_> {
type Target = Expr; type Target = Expr;
#[inline(always)] #[inline(always)]
#[must_use]
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
self.0 self.0
} }
@ -230,11 +231,11 @@ impl Engine {
continue; continue;
} }
let token = Token::lookup_symbol_from_syntax(s).or_else(|| { let token = Token::lookup_symbol_from_syntax(s).unwrap_or_else(|| {
if Token::is_reserved_keyword(s) { if Token::is_reserved_keyword(s) {
Some(Token::Reserved(Box::new(s.into()))) Token::Reserved(Box::new(s.into()))
} else { } else {
None NO_TOKEN
} }
}); });
@ -255,16 +256,16 @@ impl Engine {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
CUSTOM_SYNTAX_MARKER_FLOAT if !segments.is_empty() => s.into(), CUSTOM_SYNTAX_MARKER_FLOAT if !segments.is_empty() => s.into(),
// Standard or reserved keyword/symbol not in first position // Standard or reserved keyword/symbol not in first position
_ if !segments.is_empty() && token.is_some() => { _ if !segments.is_empty() && token != NO_TOKEN => {
// Make it a custom keyword/symbol if it is disabled or reserved // Make it a custom keyword/symbol if it is disabled or reserved
if (self if (self
.disabled_symbols .disabled_symbols
.as_ref() .as_deref()
.map_or(false, |m| m.contains(s)) .map_or(false, |m| m.contains(s))
|| token.map_or(false, |v| v.is_reserved())) || token.is_reserved())
&& !self && !self
.custom_keywords .custom_keywords
.as_ref() .as_deref()
.map_or(false, |m| m.contains_key(s)) .map_or(false, |m| m.contains_key(s))
{ {
self.custom_keywords self.custom_keywords
@ -275,10 +276,10 @@ impl Engine {
} }
// Standard keyword in first position but not disabled // Standard keyword in first position but not disabled
_ if segments.is_empty() _ if segments.is_empty()
&& token.as_ref().map_or(false, Token::is_standard_keyword) && token.is_standard_keyword()
&& !self && !self
.disabled_symbols .disabled_symbols
.as_ref() .as_deref()
.map_or(false, |m| m.contains(s)) => .map_or(false, |m| m.contains(s)) =>
{ {
return Err(LexError::ImproperSymbol( return Err(LexError::ImproperSymbol(
@ -295,12 +296,12 @@ impl Engine {
// Make it a custom keyword/symbol if it is disabled or reserved // Make it a custom keyword/symbol if it is disabled or reserved
if self if self
.disabled_symbols .disabled_symbols
.as_ref() .as_deref()
.map_or(false, |m| m.contains(s)) .map_or(false, |m| m.contains(s))
|| (token.map_or(false, |v| v.is_reserved()) || (token.is_reserved()
&& !self && !self
.custom_keywords .custom_keywords
.as_ref() .as_deref()
.map_or(false, |m| m.contains_key(s))) .map_or(false, |m| m.contains_key(s)))
{ {
self.custom_keywords self.custom_keywords

View File

@ -29,6 +29,7 @@ impl Engine {
/// # } /// # }
/// ``` /// ```
#[inline(always)] #[inline(always)]
#[must_use]
pub fn definitions(&self) -> Definitions { pub fn definitions(&self) -> Definitions {
Definitions { Definitions {
engine: self, engine: self,
@ -55,6 +56,7 @@ impl Engine {
/// # } /// # }
/// ``` /// ```
#[inline(always)] #[inline(always)]
#[must_use]
pub fn definitions_with_scope<'e>(&'e self, scope: &'e Scope<'e>) -> Definitions<'e> { pub fn definitions_with_scope<'e>(&'e self, scope: &'e Scope<'e>) -> Definitions<'e> {
Definitions { Definitions {
engine: self, engine: self,
@ -67,7 +69,6 @@ impl Engine {
/// Internal configuration for module generation. /// Internal configuration for module generation.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[non_exhaustive] #[non_exhaustive]
#[must_use]
pub struct DefinitionsConfig { pub struct DefinitionsConfig {
/// Write `module ...` headers in definition files (default `false`). /// Write `module ...` headers in definition files (default `false`).
pub write_headers: bool, pub write_headers: bool,
@ -77,6 +78,7 @@ pub struct DefinitionsConfig {
impl Default for DefinitionsConfig { impl Default for DefinitionsConfig {
#[inline(always)] #[inline(always)]
#[must_use]
fn default() -> Self { fn default() -> Self {
Self { Self {
write_headers: false, write_headers: false,
@ -89,7 +91,6 @@ impl Default for DefinitionsConfig {
/// contents of an [`Engine`]. /// contents of an [`Engine`].
/// Exported under the `internals` and `metadata` feature only. /// Exported under the `internals` and `metadata` feature only.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[must_use]
pub struct Definitions<'e> { pub struct Definitions<'e> {
/// The [`Engine`]. /// The [`Engine`].
engine: &'e Engine, engine: &'e Engine,
@ -104,12 +105,14 @@ impl Definitions<'_> {
/// Headers are always present in content that is expected to be written to a file /// Headers are always present in content that is expected to be written to a file
/// (i.e. `write_to*` and `*_file` methods). /// (i.e. `write_to*` and `*_file` methods).
#[inline(always)] #[inline(always)]
#[must_use]
pub const fn with_headers(mut self, headers: bool) -> Self { pub const fn with_headers(mut self, headers: bool) -> Self {
self.config.write_headers = headers; self.config.write_headers = headers;
self self
} }
/// Include standard packages when writing definition files. /// Include standard packages when writing definition files.
#[inline(always)] #[inline(always)]
#[must_use]
pub const fn include_standard_packages(mut self, include_standard_packages: bool) -> Self { pub const fn include_standard_packages(mut self, include_standard_packages: bool) -> Self {
self.config.include_standard_packages = include_standard_packages; self.config.include_standard_packages = include_standard_packages;
self self
@ -128,6 +131,7 @@ impl Definitions<'_> {
} }
/// Get the configuration. /// Get the configuration.
#[inline(always)] #[inline(always)]
#[must_use]
pub(crate) const fn config(&self) -> &DefinitionsConfig { pub(crate) const fn config(&self) -> &DefinitionsConfig {
&self.config &self.config
} }
@ -368,8 +372,9 @@ impl Definitions<'_> {
let mut m = self let mut m = self
.engine .engine
.global_sub_modules .global_sub_modules
.iter() .as_deref()
.flat_map(|m| m.iter()) .into_iter()
.flatten()
.map(move |(name, module)| { .map(move |(name, module)| {
( (
name.to_string(), name.to_string(),
@ -391,6 +396,7 @@ impl Definitions<'_> {
impl Module { impl Module {
/// Return definitions for all items inside the [`Module`]. /// Return definitions for all items inside the [`Module`].
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[must_use]
fn definition(&self, def: &Definitions) -> String { fn definition(&self, def: &Definitions) -> String {
let mut s = String::new(); let mut s = String::new();
self.write_definition(&mut s, def).unwrap(); self.write_definition(&mut s, def).unwrap();
@ -455,7 +461,7 @@ impl Module {
|| def || def
.engine .engine
.custom_keywords .custom_keywords
.as_ref() .as_deref()
.map_or(false, |m| m.contains_key(f.metadata.name.as_str())); .map_or(false, |m| m.contains_key(f.metadata.name.as_str()));
f.write_definition(writer, def, operator)?; f.write_definition(writer, def, operator)?;
@ -536,6 +542,7 @@ impl FuncInfo {
/// It tries to flatten types, removing `&` and `&mut`, and paths, while keeping generics. /// It tries to flatten types, removing `&` and `&mut`, and paths, while keeping generics.
/// ///
/// Associated generic types are also rewritten into regular generic type parameters. /// Associated generic types are also rewritten into regular generic type parameters.
#[must_use]
fn def_type_name<'a>(ty: &'a str, engine: &'a Engine) -> Cow<'a, str> { fn def_type_name<'a>(ty: &'a str, engine: &'a Engine) -> Cow<'a, str> {
let ty = engine.format_type_name(ty).replace("crate::", ""); let ty = engine.format_type_name(ty).replace("crate::", "");
let ty = ty.strip_prefix("&mut").unwrap_or(&*ty).trim(); let ty = ty.strip_prefix("&mut").unwrap_or(&*ty).trim();

View File

@ -26,7 +26,7 @@ impl Engine {
/// ///
/// This method is deprecated. Use [`run_file`][Engine::run_file] instead. /// This method is deprecated. Use [`run_file`][Engine::run_file] instead.
/// ///
/// This method will be removed in the next majocd cr version. /// This method will be removed in the next major version.
#[deprecated(since = "1.1.0", note = "use `run_file` instead")] #[deprecated(since = "1.1.0", note = "use `run_file` instead")]
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_family = "wasm"))] #[cfg(not(target_family = "wasm"))]
@ -137,12 +137,6 @@ impl Engine {
} }
/// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments. /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments.
/// ///
/// The following options are available:
///
/// * whether to evaluate the [`AST`] to load necessary modules before calling the function
/// * whether to rewind the [`Scope`] after the function call
/// * a value for binding to the `this` pointer (if any)
///
/// Not available under `no_function`. /// Not available under `no_function`.
/// ///
/// # Deprecated /// # Deprecated
@ -191,10 +185,10 @@ impl Engine {
/// This method will be removed in the next major version. /// This method will be removed in the next major version.
#[deprecated(since = "1.9.1", note = "use `register_fn` instead")] #[deprecated(since = "1.9.1", note = "use `register_fn` instead")]
#[inline(always)] #[inline(always)]
pub fn register_result_fn<A, R, S>( pub fn register_result_fn<A: 'static, const N: usize, const C: bool, R: Variant + Clone>(
&mut self, &mut self,
name: impl AsRef<str> + Into<Identifier>, name: impl AsRef<str> + Into<Identifier>,
func: impl RegisterNativeFunction<A, R, RhaiResultOf<S>>, func: impl RegisterNativeFunction<A, N, C, R, true>,
) -> &mut Self { ) -> &mut Self {
self.register_fn(name, func) self.register_fn(name, func)
} }
@ -212,12 +206,10 @@ impl Engine {
#[deprecated(since = "1.9.1", note = "use `register_get` instead")] #[deprecated(since = "1.9.1", note = "use `register_get` instead")]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[inline(always)] #[inline(always)]
pub fn register_get_result<T: Variant + Clone, V: Variant + Clone, S>( pub fn register_get_result<T: Variant + Clone, const C: bool, V: Variant + Clone>(
&mut self, &mut self,
name: impl AsRef<str>, name: impl AsRef<str>,
get_fn: impl RegisterNativeFunction<(Mut<T>,), V, RhaiResultOf<S>> get_fn: impl RegisterNativeFunction<(Mut<T>,), 1, C, V, true> + crate::func::SendSync + 'static,
+ crate::func::SendSync
+ 'static,
) -> &mut Self { ) -> &mut Self {
self.register_get(name, get_fn) self.register_get(name, get_fn)
} }
@ -233,10 +225,10 @@ impl Engine {
#[deprecated(since = "1.9.1", note = "use `register_set` instead")] #[deprecated(since = "1.9.1", note = "use `register_set` instead")]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[inline(always)] #[inline(always)]
pub fn register_set_result<T: Variant + Clone, V: Variant + Clone, S>( pub fn register_set_result<T: Variant + Clone, V: Variant + Clone, const C: bool, S>(
&mut self, &mut self,
name: impl AsRef<str>, name: impl AsRef<str>,
set_fn: impl RegisterNativeFunction<(Mut<T>, V), (), RhaiResultOf<S>> set_fn: impl RegisterNativeFunction<(Mut<T>, V), 2, C, (), true>
+ crate::func::SendSync + crate::func::SendSync
+ 'static, + 'static,
) -> &mut Self { ) -> &mut Self {
@ -253,12 +245,6 @@ impl Engine {
/// This method is deprecated. Use [`register_indexer_get`][Engine::register_indexer_get] instead. /// This method is deprecated. Use [`register_indexer_get`][Engine::register_indexer_get] instead.
/// ///
/// This method will be removed in the next major version. /// This method will be removed in the next major version.
///
/// # Panics
///
/// Panics if the type is [`Array`][crate::Array], [`Map`][crate::Map], [`String`],
/// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT].
/// Indexers for arrays, object maps, strings and integers cannot be registered.
#[deprecated(since = "1.9.1", note = "use `register_indexer_get` instead")] #[deprecated(since = "1.9.1", note = "use `register_indexer_get` instead")]
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[inline(always)] #[inline(always)]
@ -266,10 +252,10 @@ impl Engine {
T: Variant + Clone, T: Variant + Clone,
X: Variant + Clone, X: Variant + Clone,
V: Variant + Clone, V: Variant + Clone,
S, const C: bool,
>( >(
&mut self, &mut self,
get_fn: impl RegisterNativeFunction<(Mut<T>, X), V, RhaiResultOf<S>> get_fn: impl RegisterNativeFunction<(Mut<T>, X), 2, C, V, true>
+ crate::func::SendSync + crate::func::SendSync
+ 'static, + 'static,
) -> &mut Self { ) -> &mut Self {
@ -284,12 +270,6 @@ impl Engine {
/// This method is deprecated. Use [`register_indexer_set`][Engine::register_indexer_set] instead. /// This method is deprecated. Use [`register_indexer_set`][Engine::register_indexer_set] instead.
/// ///
/// This method will be removed in the next major version. /// This method will be removed in the next major version.
///
/// # Panics
///
/// Panics if the type is [`Array`][crate::Array], [`Map`][crate::Map], [`String`],
/// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT].
/// Indexers for arrays, object maps, strings and integers cannot be registered.
#[deprecated(since = "1.9.1", note = "use `register_indexer_set` instead")] #[deprecated(since = "1.9.1", note = "use `register_indexer_set` instead")]
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[inline(always)] #[inline(always)]
@ -297,10 +277,10 @@ impl Engine {
T: Variant + Clone, T: Variant + Clone,
X: Variant + Clone, X: Variant + Clone,
V: Variant + Clone, V: Variant + Clone,
S, const C: bool,
>( >(
&mut self, &mut self,
set_fn: impl RegisterNativeFunction<(Mut<T>, X, V), (), RhaiResultOf<S>> set_fn: impl RegisterNativeFunction<(Mut<T>, X, V), 3, C, (), true>
+ crate::func::SendSync + crate::func::SendSync
+ 'static, + 'static,
) -> &mut Self { ) -> &mut Self {
@ -316,34 +296,6 @@ impl Engine {
/// Use [`register_custom_syntax_with_state_raw`][Engine::register_custom_syntax_with_state_raw] instead. /// Use [`register_custom_syntax_with_state_raw`][Engine::register_custom_syntax_with_state_raw] instead.
/// ///
/// This method will be removed in the next major version. /// This method will be removed in the next major version.
///
/// # WARNING - Low Level API
///
/// This function is very low level.
///
/// * `scope_may_be_changed` specifies variables have been added/removed by this custom syntax.
/// * `parse` is the parsing function.
/// * `func` is the implementation function.
///
/// All custom keywords used as symbols must be manually registered via [`Engine::register_custom_operator`].
/// Otherwise, they won't be recognized.
///
/// # Parsing Function Signature
///
/// The parsing function has the following signature:
///
/// `Fn(symbols: &[ImmutableString], look_ahead: &str) -> Result<Option<ImmutableString>, ParseError>`
///
/// where:
/// * `symbols`: a slice of symbols that have been parsed so far, possibly containing `$expr$` and/or `$block$`;
/// `$ident$` and other literal markers are replaced by the actual text
/// * `look_ahead`: a string slice containing the next symbol that is about to be read
///
/// ## Return value
///
/// * `Ok(None)`: parsing complete and there are no more symbols to match.
/// * `Ok(Some(symbol))`: the next symbol to match, which can also be `$expr$`, `$ident$` or `$block$`.
/// * `Err(ParseError)`: error that is reflected back to the [`Engine`], normally `ParseError(ParseErrorType::BadInput(LexError::ImproperSymbol(message)), Position::NONE)` to indicate a syntax error, but it can be any [`ParseError`][crate::ParseError].
#[deprecated( #[deprecated(
since = "1.11.0", since = "1.11.0",
note = "use `register_custom_syntax_with_state_raw` instead" note = "use `register_custom_syntax_with_state_raw` instead"
@ -368,6 +320,24 @@ impl Engine {
move |context, expressions, _| func(context, expressions), move |context, expressions, _| func(context, expressions),
) )
} }
/// _(internals)_ Evaluate a list of statements with no `this` pointer.
/// Exported under the `internals` feature only.
///
/// # Deprecated
///
/// This method is deprecated. It will be removed in the next major version.
#[cfg(feature = "internals")]
#[inline(always)]
#[deprecated(since = "1.12.0")]
pub fn eval_statements_raw(
&self,
global: &mut crate::eval::GlobalRuntimeState,
caches: &mut crate::eval::Caches,
scope: &mut Scope,
statements: &[crate::ast::Stmt],
) -> RhaiResult {
self.eval_global_statements(global, caches, scope, statements)
}
} }
impl Dynamic { impl Dynamic {
@ -544,10 +514,15 @@ impl<'a, T: Variant + Clone> TypeBuilder<'a, T> {
/// This method will be removed in the next major version. /// This method will be removed in the next major version.
#[deprecated(since = "1.9.1", note = "use `with_fn` instead")] #[deprecated(since = "1.9.1", note = "use `with_fn` instead")]
#[inline(always)] #[inline(always)]
pub fn with_result_fn<N, A, F, R, S>(&mut self, name: N, method: F) -> &mut Self pub fn with_result_fn<S, A: 'static, const N: usize, const C: bool, R, F>(
&mut self,
name: S,
method: F,
) -> &mut Self
where where
N: AsRef<str> + Into<Identifier>, S: AsRef<str> + Into<Identifier>,
F: RegisterNativeFunction<A, R, RhaiResultOf<S>>, R: Variant + Clone,
F: RegisterNativeFunction<A, N, C, R, true>,
{ {
self.with_fn(name, method) self.with_fn(name, method)
} }
@ -566,12 +541,10 @@ impl<'a, T: Variant + Clone> TypeBuilder<'a, T> {
#[deprecated(since = "1.9.1", note = "use `with_get` instead")] #[deprecated(since = "1.9.1", note = "use `with_get` instead")]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[inline(always)] #[inline(always)]
pub fn with_get_result<V: Variant + Clone, S>( pub fn with_get_result<V: Variant + Clone, const C: bool>(
&mut self, &mut self,
name: impl AsRef<str>, name: impl AsRef<str>,
get_fn: impl RegisterNativeFunction<(Mut<T>,), V, RhaiResultOf<S>> get_fn: impl RegisterNativeFunction<(Mut<T>,), 1, C, V, true> + crate::func::SendSync + 'static,
+ crate::func::SendSync
+ 'static,
) -> &mut Self { ) -> &mut Self {
self.with_get(name, get_fn) self.with_get(name, get_fn)
} }
@ -588,10 +561,10 @@ impl<'a, T: Variant + Clone> TypeBuilder<'a, T> {
#[deprecated(since = "1.9.1", note = "use `with_set` instead")] #[deprecated(since = "1.9.1", note = "use `with_set` instead")]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[inline(always)] #[inline(always)]
pub fn with_set_result<V: Variant + Clone, S>( pub fn with_set_result<V: Variant + Clone, const C: bool>(
&mut self, &mut self,
name: impl AsRef<str>, name: impl AsRef<str>,
set_fn: impl RegisterNativeFunction<(Mut<T>, V), (), RhaiResultOf<S>> set_fn: impl RegisterNativeFunction<(Mut<T>, V), 2, C, (), true>
+ crate::func::SendSync + crate::func::SendSync
+ 'static, + 'static,
) -> &mut Self { ) -> &mut Self {
@ -612,9 +585,9 @@ impl<'a, T: Variant + Clone> TypeBuilder<'a, T> {
#[deprecated(since = "1.9.1", note = "use `with_indexer_get` instead")] #[deprecated(since = "1.9.1", note = "use `with_indexer_get` instead")]
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[inline(always)] #[inline(always)]
pub fn with_indexer_get_result<X: Variant + Clone, V: Variant + Clone, S>( pub fn with_indexer_get_result<X: Variant + Clone, V: Variant + Clone, const C: bool>(
&mut self, &mut self,
get_fn: impl RegisterNativeFunction<(Mut<T>, X), V, RhaiResultOf<S>> get_fn: impl RegisterNativeFunction<(Mut<T>, X), 2, C, V, true>
+ crate::func::SendSync + crate::func::SendSync
+ 'static, + 'static,
) -> &mut Self { ) -> &mut Self {
@ -633,9 +606,9 @@ impl<'a, T: Variant + Clone> TypeBuilder<'a, T> {
#[deprecated(since = "1.9.1", note = "use `with_indexer_set` instead")] #[deprecated(since = "1.9.1", note = "use `with_indexer_set` instead")]
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[inline(always)] #[inline(always)]
pub fn with_indexer_set_result<X: Variant + Clone, V: Variant + Clone, S>( pub fn with_indexer_set_result<X: Variant + Clone, V: Variant + Clone, const C: bool>(
&mut self, &mut self,
set_fn: impl RegisterNativeFunction<(Mut<T>, X, V), (), RhaiResultOf<S>> set_fn: impl RegisterNativeFunction<(Mut<T>, X, V), 3, C, (), true>
+ crate::func::SendSync + crate::func::SendSync
+ 'static, + 'static,
) -> &mut Self { ) -> &mut Self {

View File

@ -5,7 +5,7 @@ use crate::func::native::locked_write;
use crate::parser::ParseState; use crate::parser::ParseState;
use crate::types::dynamic::Variant; use crate::types::dynamic::Variant;
use crate::{ use crate::{
reify, Dynamic, Engine, OptimizationLevel, Position, RhaiResult, RhaiResultOf, Scope, AST, ERR, Dynamic, Engine, OptimizationLevel, Position, RhaiResult, RhaiResultOf, Scope, AST, ERR,
}; };
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -121,15 +121,14 @@ impl Engine {
let ast = { let ast = {
let interned_strings = &mut *locked_write(&self.interned_strings); let interned_strings = &mut *locked_write(&self.interned_strings);
let (stream, tokenizer_control) = let (stream, tc) = self.lex_raw(&scripts, self.token_mapper.as_deref());
self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref));
let mut state = ParseState::new(scope, interned_strings, tokenizer_control); let state = &mut ParseState::new(scope, interned_strings, tc);
// No need to optimize a lone expression // No need to optimize a lone expression
self.parse_global_expr( self.parse_global_expr(
&mut stream.peekable(), stream.peekable(),
&mut state, state,
|_| {}, |_| {},
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
OptimizationLevel::None, OptimizationLevel::None,
@ -243,7 +242,7 @@ impl Engine {
let result = self.eval_global_statements(global, caches, scope, statements); let result = self.eval_global_statements(global, caches, scope, statements);
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
if self.debugger.is_some() { if self.is_debugger_registered() {
global.debugger_mut().status = crate::eval::DebuggerStatus::Terminate; global.debugger_mut().status = crate::eval::DebuggerStatus::Terminate;
let mut this = Dynamic::NULL; let mut this = Dynamic::NULL;
let node = &crate::ast::Stmt::Noop(Position::NONE); let node = &crate::ast::Stmt::Noop(Position::NONE);
@ -263,25 +262,6 @@ impl Engine {
result result
} }
/// _(internals)_ Evaluate a list of statements with no `this` pointer.
/// Exported under the `internals` feature only.
///
/// This is commonly used to evaluate a list of statements in an [`AST`] or a script function body.
///
/// # WARNING - Low Level API
///
/// This function is very low level.
#[cfg(feature = "internals")]
#[inline(always)]
pub fn eval_statements_raw(
&self,
global: &mut GlobalRuntimeState,
caches: &mut Caches,
scope: &mut Scope,
statements: &[crate::ast::Stmt],
) -> RhaiResult {
self.eval_global_statements(global, caches, scope, statements)
}
} }
/// Evaluate a string as a script, returning the result value or an error. /// Evaluate a string as a script, returning the result value or an error.

View File

@ -349,7 +349,9 @@ impl Engine {
#[inline(always)] #[inline(always)]
pub fn register_debugger( pub fn register_debugger(
&mut self, &mut self,
init: impl Fn(&Self) -> Dynamic + SendSync + 'static, init: impl Fn(&Self, crate::debugger::Debugger) -> crate::debugger::Debugger
+ SendSync
+ 'static,
callback: impl Fn( callback: impl Fn(
EvalContext, EvalContext,
crate::eval::DebuggerEvent, crate::eval::DebuggerEvent,
@ -360,7 +362,7 @@ impl Engine {
+ SendSync + SendSync
+ 'static, + 'static,
) -> &mut Self { ) -> &mut Self {
self.debugger = Some(Box::new((Box::new(init), Box::new(callback)))); self.debugger_interface = Some(Box::new((Box::new(init), Box::new(callback))));
self self
} }
} }

View File

@ -64,8 +64,8 @@ impl Engine {
let (stream, tokenizer_control) = self.lex_raw( let (stream, tokenizer_control) = self.lex_raw(
&scripts, &scripts,
if has_null { Some(if has_null {
Some(&|token, _, _| { &|token, _, _| {
match token { match token {
// `null` => `()` // `null` => `()`
Token::Reserved(s) if &*s == "null" => Token::Unit, Token::Reserved(s) if &*s == "null" => Token::Unit,
@ -86,9 +86,9 @@ impl Engine {
// All others // All others
_ => token, _ => token,
} }
}) }
} else { } else {
Some(&|token, _, _| { &|token, _, _| {
match token { match token {
Token::Reserved(s) if &*s == "null" => Token::LexError( Token::Reserved(s) if &*s == "null" => Token::LexError(
LexError::ImproperSymbol("null".to_string(), String::new()).into(), LexError::ImproperSymbol("null".to_string(), String::new()).into(),
@ -97,34 +97,31 @@ impl Engine {
Token::LeftBrace => Token::MapStart, Token::LeftBrace => Token::MapStart,
// Disallowed syntax // Disallowed syntax
t @ (Token::Unit | Token::MapStart) => Token::LexError( t @ (Token::Unit | Token::MapStart) => Token::LexError(
LexError::ImproperSymbol( LexError::ImproperSymbol(t.literal_syntax().to_string(), String::new())
t.literal_syntax().to_string(), .into(),
"Invalid JSON syntax".to_string(),
)
.into(),
), ),
Token::InterpolatedString(..) => Token::LexError( Token::InterpolatedString(..) => Token::LexError(
LexError::ImproperSymbol( LexError::ImproperSymbol(
"interpolated string".to_string(), "interpolated string".to_string(),
"Invalid JSON syntax".to_string(), String::new(),
) )
.into(), .into(),
), ),
// All others // All others
_ => token, _ => token,
} }
}) }
}, }),
); );
let ast = { let ast = {
let scope = Scope::new(); let scope = Scope::new();
let interned_strings = &mut *locked_write(&self.interned_strings); let interned_strings = &mut *locked_write(&self.interned_strings);
let mut state = ParseState::new(&scope, interned_strings, tokenizer_control); let state = &mut ParseState::new(&scope, interned_strings, tokenizer_control);
self.parse_global_expr( self.parse_global_expr(
&mut stream.peekable(), stream.peekable(),
&mut state, state,
|s| s.flags |= ParseSettingFlags::DISALLOW_UNQUOTED_MAP_PROPERTIES, |s| s.flags |= ParseSettingFlags::DISALLOW_UNQUOTED_MAP_PROPERTIES,
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
OptimizationLevel::None, OptimizationLevel::None,

View File

@ -6,23 +6,20 @@ use std::num::{NonZeroU64, NonZeroUsize};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
#[cfg(debug_assertions)]
pub mod default_limits { pub mod default_limits {
#[cfg(debug_assertions)]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub const MAX_CALL_STACK_DEPTH: usize = 8; pub const MAX_CALL_STACK_DEPTH: usize = 8;
#[cfg(debug_assertions)]
pub const MAX_EXPR_DEPTH: usize = 32; pub const MAX_EXPR_DEPTH: usize = 32;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(debug_assertions)]
pub const MAX_FUNCTION_EXPR_DEPTH: usize = 16; pub const MAX_FUNCTION_EXPR_DEPTH: usize = 16;
}
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
pub mod default_limits {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub const MAX_CALL_STACK_DEPTH: usize = 64; pub const MAX_CALL_STACK_DEPTH: usize = 64;
#[cfg(not(debug_assertions))]
pub const MAX_EXPR_DEPTH: usize = 64; pub const MAX_EXPR_DEPTH: usize = 64;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(not(debug_assertions))]
pub const MAX_FUNCTION_EXPR_DEPTH: usize = 32; pub const MAX_FUNCTION_EXPR_DEPTH: usize = 32;
} }
@ -55,7 +52,7 @@ pub struct Limits {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub max_modules: usize, pub max_modules: usize,
/// Maximum length of a [string][crate::ImmutableString]. /// Maximum length of a [string][crate::ImmutableString].
pub max_string_size: Option<NonZeroUsize>, pub max_string_len: Option<NonZeroUsize>,
/// Maximum length of an [array][crate::Array]. /// Maximum length of an [array][crate::Array].
/// ///
/// Not available under `no_index`. /// Not available under `no_index`.
@ -83,7 +80,7 @@ impl Limits {
max_operations: None, max_operations: None,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
max_modules: usize::MAX, max_modules: usize::MAX,
max_string_size: None, max_string_len: None,
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
max_array_size: None, max_array_size: None,
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -104,7 +101,7 @@ impl Engine {
/// Is there a data size limit set? /// Is there a data size limit set?
#[inline(always)] #[inline(always)]
pub(crate) const fn has_data_size_limit(&self) -> bool { pub(crate) const fn has_data_size_limit(&self) -> bool {
self.limits.max_string_size.is_some() self.limits.max_string_len.is_some()
|| { || {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
{ {
@ -222,19 +219,19 @@ impl Engine {
#[cfg(feature = "no_function")] #[cfg(feature = "no_function")]
return 0; return 0;
} }
/// Set the maximum length of [strings][crate::ImmutableString] (0 for unlimited). /// Set the maximum length, in bytes, of [strings][crate::ImmutableString] (0 for unlimited).
/// ///
/// Not available under `unchecked`. /// Not available under `unchecked`.
#[inline(always)] #[inline(always)]
pub fn set_max_string_size(&mut self, max_size: usize) -> &mut Self { pub fn set_max_string_size(&mut self, max_len: usize) -> &mut Self {
self.limits.max_string_size = NonZeroUsize::new(max_size); self.limits.max_string_len = NonZeroUsize::new(max_len);
self self
} }
/// The maximum length of [strings][crate::ImmutableString] (0 for unlimited). /// The maximum length, in bytes, of [strings][crate::ImmutableString] (0 for unlimited).
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn max_string_size(&self) -> usize { pub const fn max_string_size(&self) -> usize {
match self.limits.max_string_size { match self.limits.max_string_len {
Some(n) => n.get(), Some(n) => n.get(),
None => 0, None => 0,
} }

View File

@ -0,0 +1,71 @@
//! Placeholder settings for [`Engine`]'s limitations.
#![cfg(feature = "unchecked")]
use crate::Engine;
impl Engine {
/// The maximum levels of function calls allowed for a script.
///
/// Always returns [`usize::MAX`].
#[inline(always)]
#[must_use]
pub const fn max_call_levels(&self) -> usize {
usize::MAX
}
/// The maximum number of operations allowed for a script to run (0 for unlimited).
///
/// Always returns zero.
#[inline(always)]
#[must_use]
pub const fn max_operations(&self) -> u64 {
0
}
/// The maximum number of imported [modules][crate::Module] allowed for a script.
///
/// Always returns [`usize::MAX`].
#[inline(always)]
#[must_use]
pub const fn max_modules(&self) -> usize {
usize::MAX
}
/// The depth limit for expressions (0 for unlimited).
///
/// Always returns zero.
#[inline(always)]
#[must_use]
pub const fn max_expr_depth(&self) -> usize {
0
}
/// The depth limit for expressions in functions (0 for unlimited).
///
/// Always returns zero.
#[inline(always)]
#[must_use]
pub const fn max_function_expr_depth(&self) -> usize {
0
}
/// The maximum length of [strings][crate::ImmutableString] (0 for unlimited).
///
/// Always returns zero.
#[inline(always)]
#[must_use]
pub const fn max_string_size(&self) -> usize {
0
}
/// The maximum length of [arrays][crate::Array] (0 for unlimited).
///
/// Always returns zero.
#[inline(always)]
#[must_use]
pub const fn max_array_size(&self) -> usize {
0
}
/// The maximum size of [object maps][crate::Map] (0 for unlimited).
///
/// Always returns zero.
#[inline(always)]
#[must_use]
pub const fn max_map_size(&self) -> usize {
0
}
}

View File

@ -1,7 +1,5 @@
//! Module defining the public API of the Rhai engine. //! Module defining the public API of the Rhai engine.
pub mod type_names;
pub mod eval; pub mod eval;
pub mod run; pub mod run;
@ -21,18 +19,21 @@ pub mod options;
pub mod optimize; pub mod optimize;
pub mod limits; pub mod limits;
pub mod limits_unchecked;
pub mod events; pub mod events;
pub mod custom_syntax; pub mod type_names;
pub mod deprecated; pub mod custom_syntax;
pub mod build_type; pub mod build_type;
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
pub mod definitions; pub mod definitions;
pub mod deprecated;
use crate::{Dynamic, Engine, Identifier}; use crate::{Dynamic, Engine, Identifier};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
@ -167,7 +168,7 @@ impl Engine {
Some(token) if token.is_standard_keyword() => { Some(token) if token.is_standard_keyword() => {
if !self if !self
.disabled_symbols .disabled_symbols
.as_ref() .as_deref()
.map_or(false, |m| m.contains(token.literal_syntax())) .map_or(false, |m| m.contains(token.literal_syntax()))
{ {
return Err(format!("'{keyword}' is a reserved keyword")); return Err(format!("'{keyword}' is a reserved keyword"));
@ -177,7 +178,7 @@ impl Engine {
Some(token) if token.is_standard_symbol() => { Some(token) if token.is_standard_symbol() => {
if !self if !self
.disabled_symbols .disabled_symbols
.as_ref() .as_deref()
.map_or(false, |m| m.contains(token.literal_syntax())) .map_or(false, |m| m.contains(token.literal_syntax()))
{ {
return Err(format!("'{keyword}' is a reserved operator")); return Err(format!("'{keyword}' is a reserved operator"));
@ -187,7 +188,7 @@ impl Engine {
Some(token) Some(token)
if !self if !self
.disabled_symbols .disabled_symbols
.as_ref() .as_deref()
.map_or(false, |m| m.contains(token.literal_syntax())) => .map_or(false, |m| m.contains(token.literal_syntax())) =>
{ {
return Err(format!("'{keyword}' is a reserved symbol")) return Err(format!("'{keyword}' is a reserved symbol"))
@ -223,71 +224,3 @@ impl Engine {
self self
} }
} }
#[cfg(feature = "unchecked")]
impl Engine {
/// The maximum levels of function calls allowed for a script.
///
/// Always returns [`usize::MAX`] under `unchecked`.
#[inline(always)]
#[must_use]
pub const fn max_call_levels(&self) -> usize {
usize::MAX
}
/// The maximum number of operations allowed for a script to run (0 for unlimited).
///
/// Always returns zero under `unchecked`.
#[inline(always)]
#[must_use]
pub const fn max_operations(&self) -> u64 {
0
}
/// The maximum number of imported [modules][crate::Module] allowed for a script.
///
/// Always returns [`usize::MAX`] under `unchecked`.
#[inline(always)]
#[must_use]
pub const fn max_modules(&self) -> usize {
usize::MAX
}
/// The depth limit for expressions (0 for unlimited).
///
/// Always returns zero under `unchecked`.
#[inline(always)]
#[must_use]
pub const fn max_expr_depth(&self) -> usize {
0
}
/// The depth limit for expressions in functions (0 for unlimited).
///
/// Always returns zero under `unchecked`.
#[inline(always)]
#[must_use]
pub const fn max_function_expr_depth(&self) -> usize {
0
}
/// The maximum length of [strings][crate::ImmutableString] (0 for unlimited).
///
/// Always returns zero under `unchecked`.
#[inline(always)]
#[must_use]
pub const fn max_string_size(&self) -> usize {
0
}
/// The maximum length of [arrays][crate::Array] (0 for unlimited).
///
/// Always returns zero under `unchecked`.
#[inline(always)]
#[must_use]
pub const fn max_array_size(&self) -> usize {
0
}
/// The maximum size of [object maps][crate::Map] (0 for unlimited).
///
/// Always returns zero under `unchecked`.
#[inline(always)]
#[must_use]
pub const fn max_map_size(&self) -> usize {
0
}
}

View File

@ -51,20 +51,14 @@ impl Engine {
) -> AST { ) -> AST {
let mut ast = ast; let mut ast = ast;
#[cfg(not(feature = "no_function"))] let mut _new_ast = self.optimize_into_ast(
let functions = ast
.shared_lib()
.iter_fn()
.filter(|f| f.func.is_script())
.map(|f| f.func.get_script_fn_def().unwrap().clone())
.collect();
let mut _new_ast = crate::optimizer::optimize_into_ast(
self,
scope, scope,
ast.take_statements(), ast.take_statements(),
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
functions, ast.shared_lib()
.iter_fn()
.map(|f| f.func.get_script_fn_def().cloned().expect("`ScriptFnDef"))
.collect(),
optimization_level, optimization_level,
); );

View File

@ -56,7 +56,14 @@ impl Engine {
/// # } /// # }
/// ``` /// ```
#[inline] #[inline]
pub fn register_fn<A, R, S, F: RegisterNativeFunction<A, R, S>>( pub fn register_fn<
A: 'static,
const N: usize,
const C: bool,
R: Variant + Clone,
const L: bool,
F: RegisterNativeFunction<A, N, C, R, L>,
>(
&mut self, &mut self,
name: impl AsRef<str> + Into<Identifier>, name: impl AsRef<str> + Into<Identifier>,
func: F, func: F,
@ -83,13 +90,24 @@ impl Engine {
#[cfg(not(feature = "metadata"))] #[cfg(not(feature = "metadata"))]
let param_type_names: Option<&[&str]> = None; let param_type_names: Option<&[&str]> = None;
let fn_name = name.as_ref();
let no_const = false;
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
let no_const = no_const || (F::num_params() == 3 && fn_name == crate::engine::FN_IDX_SET);
#[cfg(not(feature = "no_object"))]
let no_const =
no_const || (F::num_params() == 2 && fn_name.starts_with(crate::engine::FN_SET));
let func = func.into_callable_function(fn_name.into(), no_const);
self.global_namespace_mut().set_fn( self.global_namespace_mut().set_fn(
name, name,
FnNamespace::Global, FnNamespace::Global,
FnAccess::Public, FnAccess::Public,
param_type_names, param_type_names,
param_types, param_types,
func.into_callable_function(), func,
); );
self self
} }
@ -299,10 +317,10 @@ impl Engine {
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[inline(always)] #[inline(always)]
pub fn register_get<T: Variant + Clone, V: Variant + Clone, S>( pub fn register_get<T: Variant + Clone, const C: bool, V: Variant + Clone, const L: bool>(
&mut self, &mut self,
name: impl AsRef<str>, name: impl AsRef<str>,
get_fn: impl RegisterNativeFunction<(Mut<T>,), V, S> + SendSync + 'static, get_fn: impl RegisterNativeFunction<(Mut<T>,), 1, C, V, L> + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
self.register_fn(crate::engine::make_getter(name.as_ref()).as_str(), get_fn) self.register_fn(crate::engine::make_getter(name.as_ref()).as_str(), get_fn)
} }
@ -349,10 +367,10 @@ impl Engine {
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[inline(always)] #[inline(always)]
pub fn register_set<T: Variant + Clone, V: Variant + Clone, S>( pub fn register_set<T: Variant + Clone, const C: bool, V: Variant + Clone, const L: bool>(
&mut self, &mut self,
name: impl AsRef<str>, name: impl AsRef<str>,
set_fn: impl RegisterNativeFunction<(Mut<T>, V), (), S> + SendSync + 'static, set_fn: impl RegisterNativeFunction<(Mut<T>, V), 2, C, (), L> + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
self.register_fn(crate::engine::make_setter(name.as_ref()).as_str(), set_fn) self.register_fn(crate::engine::make_setter(name.as_ref()).as_str(), set_fn)
} }
@ -403,11 +421,18 @@ impl Engine {
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[inline(always)] #[inline(always)]
pub fn register_get_set<T: Variant + Clone, V: Variant + Clone, S1, S2>( pub fn register_get_set<
T: Variant + Clone,
const C1: bool,
const C2: bool,
V: Variant + Clone,
const L1: bool,
const L2: bool,
>(
&mut self, &mut self,
name: impl AsRef<str>, name: impl AsRef<str>,
get_fn: impl RegisterNativeFunction<(Mut<T>,), V, S1> + SendSync + 'static, get_fn: impl RegisterNativeFunction<(Mut<T>,), 1, C1, V, L1> + SendSync + 'static,
set_fn: impl RegisterNativeFunction<(Mut<T>, V), (), S2> + SendSync + 'static, set_fn: impl RegisterNativeFunction<(Mut<T>, V), 2, C2, (), L2> + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
self.register_get(&name, get_fn).register_set(&name, set_fn) self.register_get(&name, get_fn).register_set(&name, set_fn)
} }
@ -462,9 +487,15 @@ impl Engine {
/// ``` /// ```
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[inline] #[inline]
pub fn register_indexer_get<T: Variant + Clone, X: Variant + Clone, V: Variant + Clone, S>( pub fn register_indexer_get<
T: Variant + Clone,
X: Variant + Clone,
const C: bool,
V: Variant + Clone,
const L: bool,
>(
&mut self, &mut self,
get_fn: impl RegisterNativeFunction<(Mut<T>, X), V, S> + SendSync + 'static, get_fn: impl RegisterNativeFunction<(Mut<T>, X), 2, C, V, L> + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
if TypeId::of::<T>() == TypeId::of::<crate::Array>() { if TypeId::of::<T>() == TypeId::of::<crate::Array>() {
@ -537,9 +568,15 @@ impl Engine {
/// ``` /// ```
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[inline] #[inline]
pub fn register_indexer_set<T: Variant + Clone, X: Variant + Clone, V: Variant + Clone, S>( pub fn register_indexer_set<
T: Variant + Clone,
X: Variant + Clone,
const C: bool,
V: Variant + Clone,
const L: bool,
>(
&mut self, &mut self,
set_fn: impl RegisterNativeFunction<(Mut<T>, X, V), (), S> + SendSync + 'static, set_fn: impl RegisterNativeFunction<(Mut<T>, X, V), 3, C, (), L> + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
if TypeId::of::<T>() == TypeId::of::<crate::Array>() { if TypeId::of::<T>() == TypeId::of::<crate::Array>() {
@ -616,13 +653,15 @@ impl Engine {
pub fn register_indexer_get_set< pub fn register_indexer_get_set<
T: Variant + Clone, T: Variant + Clone,
X: Variant + Clone, X: Variant + Clone,
const C1: bool,
const C2: bool,
V: Variant + Clone, V: Variant + Clone,
S1, const L1: bool,
S2, const L2: bool,
>( >(
&mut self, &mut self,
get_fn: impl RegisterNativeFunction<(Mut<T>, X), V, S1> + SendSync + 'static, get_fn: impl RegisterNativeFunction<(Mut<T>, X), 2, C1, V, L1> + SendSync + 'static,
set_fn: impl RegisterNativeFunction<(Mut<T>, X, V), (), S2> + SendSync + 'static, set_fn: impl RegisterNativeFunction<(Mut<T>, X, V), 3, C2, (), L2> + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
self.register_indexer_get(get_fn) self.register_indexer_get(get_fn)
.register_indexer_set(set_fn) .register_indexer_set(set_fn)
@ -743,7 +782,7 @@ impl Engine {
signatures.extend(self.global_namespace().gen_fn_signatures()); signatures.extend(self.global_namespace().gen_fn_signatures());
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
for (name, m) in self.global_sub_modules.iter().flat_map(|m| m.iter()) { for (name, m) in self.global_sub_modules.as_deref().into_iter().flatten() {
signatures.extend(m.gen_fn_signatures().map(|f| format!("{name}::{f}"))); signatures.extend(m.gen_fn_signatures().map(|f| format!("{name}::{f}")));
} }

View File

@ -58,14 +58,10 @@ impl Engine {
pub fn run_with_scope(&self, scope: &mut Scope, script: &str) -> RhaiResultOf<()> { pub fn run_with_scope(&self, scope: &mut Scope, script: &str) -> RhaiResultOf<()> {
let scripts = [script]; let scripts = [script];
let ast = { let ast = {
let (stream, tc) = self.lex_raw(&scripts, self.token_mapper.as_deref());
let interned_strings = &mut *locked_write(&self.interned_strings); let interned_strings = &mut *locked_write(&self.interned_strings);
let state = &mut ParseState::new(scope, interned_strings, tc);
let (stream, tokenizer_control) = self.parse(stream.peekable(), state, self.optimization_level)?
self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref));
let mut state = ParseState::new(scope, interned_strings, tokenizer_control);
self.parse(&mut stream.peekable(), &mut state, self.optimization_level)?
}; };
self.run_ast_with_scope(scope, &ast) self.run_ast_with_scope(scope, &ast)
} }
@ -131,19 +127,23 @@ impl Engine {
} }
let statements = ast.statements(); let statements = ast.statements();
if !statements.is_empty() {
self.eval_global_statements(global, caches, scope, statements)?; let result = if !statements.is_empty() {
} self.eval_global_statements(global, caches, scope, statements)
.map(|_| ())
} else {
Ok(())
};
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
if self.debugger.is_some() { if self.is_debugger_registered() {
global.debugger_mut().status = crate::eval::DebuggerStatus::Terminate; global.debugger_mut().status = crate::eval::DebuggerStatus::Terminate;
let mut this = crate::Dynamic::NULL; let mut this = crate::Dynamic::NULL;
let node = &crate::ast::Stmt::Noop(crate::Position::NONE); let node = &crate::ast::Stmt::Noop(crate::Position::NONE);
self.run_debugger(global, caches, scope, &mut this, node)?; self.run_debugger(global, caches, scope, &mut this, node)?;
} }
Ok(()) result
} }
} }

View File

@ -204,8 +204,9 @@ impl Engine {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
return self return self
.global_sub_modules .global_sub_modules
.iter() .as_deref()
.flat_map(|m| m.iter()) .into_iter()
.flatten()
.find_map(|(_, m)| m.get_custom_type(name)); .find_map(|(_, m)| m.get_custom_type(name));
#[cfg(feature = "no_module")] #[cfg(feature = "no_module")]
return None; return None;
@ -238,8 +239,9 @@ impl Engine {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
return self return self
.global_sub_modules .global_sub_modules
.iter() .as_deref()
.flat_map(|m| m.iter()) .into_iter()
.flatten()
.find_map(|(_, m)| m.get_custom_type(name)); .find_map(|(_, m)| m.get_custom_type(name));
#[cfg(feature = "no_module")] #[cfg(feature = "no_module")]
return None; return None;

View File

@ -1,9 +1,9 @@
//! Module defining script expressions. //! Module defining script expressions.
use super::{ASTFlags, ASTNode, Ident, Stmt, StmtBlock}; use super::{ASTFlags, ASTNode, Ident, Namespace, Stmt, StmtBlock};
use crate::engine::{KEYWORD_FN_PTR, OP_EXCLUSIVE_RANGE, OP_INCLUSIVE_RANGE}; use crate::engine::{KEYWORD_FN_PTR, OP_EXCLUSIVE_RANGE, OP_INCLUSIVE_RANGE};
use crate::func::hashing::ALT_ZERO_HASH; use crate::func::hashing::ALT_ZERO_HASH;
use crate::tokenizer::Token; use crate::tokenizer::{Token, NO_TOKEN};
use crate::types::dynamic::Union; use crate::types::dynamic::Union;
use crate::{ use crate::{
calc_fn_hash, Dynamic, FnPtr, Identifier, ImmutableString, Position, SmartString, StaticVec, calc_fn_hash, Dynamic, FnPtr, Identifier, ImmutableString, Position, SmartString, StaticVec,
@ -197,8 +197,7 @@ impl FnCallHashes {
#[derive(Clone, Hash)] #[derive(Clone, Hash)]
pub struct FnCallExpr { pub struct FnCallExpr {
/// Namespace of the function, if any. /// Namespace of the function, if any.
#[cfg(not(feature = "no_module"))] pub namespace: Namespace,
pub namespace: super::Namespace,
/// Function name. /// Function name.
pub name: ImmutableString, pub name: ImmutableString,
/// Pre-calculated hashes. /// Pre-calculated hashes.
@ -208,7 +207,8 @@ pub struct FnCallExpr {
/// Does this function call capture the parent scope? /// Does this function call capture the parent scope?
pub capture_parent_scope: bool, pub capture_parent_scope: bool,
/// Is this function call a native operator? /// Is this function call a native operator?
pub op_token: Option<Token>, /// Otherwise set to [`Token::NONE`].
pub op_token: Token,
} }
impl fmt::Debug for FnCallExpr { impl fmt::Debug for FnCallExpr {
@ -216,15 +216,14 @@ impl fmt::Debug for FnCallExpr {
#[inline(never)] #[inline(never)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut ff = f.debug_struct("FnCallExpr"); let mut ff = f.debug_struct("FnCallExpr");
#[cfg(not(feature = "no_module"))]
if !self.namespace.is_empty() { if !self.namespace.is_empty() {
ff.field("namespace", &self.namespace); ff.field("namespace", &self.namespace);
} }
ff.field("hash", &self.hashes) ff.field("hash", &self.hashes)
.field("name", &self.name) .field("name", &self.name)
.field("args", &self.args); .field("args", &self.args);
if let Some(ref token) = self.op_token { if self.op_token != NO_TOKEN {
ff.field("op_token", token); ff.field("op_token", &self.op_token);
} }
if self.capture_parent_scope { if self.capture_parent_scope {
ff.field("capture_parent_scope", &self.capture_parent_scope); ff.field("capture_parent_scope", &self.capture_parent_scope);
@ -240,10 +239,7 @@ impl FnCallExpr {
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn is_qualified(&self) -> bool { pub fn is_qualified(&self) -> bool {
#[cfg(not(feature = "no_module"))] !self.namespace.is_empty()
return !self.namespace.is_empty();
#[cfg(feature = "no_module")]
return false;
} }
/// Convert this into an [`Expr::FnCall`]. /// Convert this into an [`Expr::FnCall`].
#[inline(always)] #[inline(always)]
@ -303,9 +299,7 @@ pub enum Expr {
/// majority of cases (unless there are more than 255 variables defined!). /// majority of cases (unless there are more than 255 variables defined!).
/// This is to avoid reading a pointer redirection during each variable access. /// This is to avoid reading a pointer redirection during each variable access.
Variable( Variable(
#[cfg(not(feature = "no_module"))] Box<(Option<NonZeroUsize>, Namespace, u64, ImmutableString)>,
Box<(Option<NonZeroUsize>, super::Namespace, u64, ImmutableString)>,
#[cfg(feature = "no_module")] Box<(Option<NonZeroUsize>, (), u64, ImmutableString)>,
Option<NonZeroU8>, Option<NonZeroU8>,
Position, Position,
), ),
@ -583,13 +577,12 @@ impl Expr {
Union::FnPtr(f, ..) if !f.is_curried() => Self::FnCall( Union::FnPtr(f, ..) if !f.is_curried() => Self::FnCall(
FnCallExpr { FnCallExpr {
#[cfg(not(feature = "no_module"))] namespace: Namespace::NONE,
namespace: super::Namespace::NONE,
name: KEYWORD_FN_PTR.into(), name: KEYWORD_FN_PTR.into(),
hashes: calc_fn_hash(None, f.fn_name(), 1).into(), hashes: calc_fn_hash(None, f.fn_name(), 1).into(),
args: once(Self::StringConstant(f.fn_name().into(), pos)).collect(), args: once(Self::StringConstant(f.fn_name().into(), pos)).collect(),
capture_parent_scope: false, capture_parent_scope: false,
op_token: None, op_token: NO_TOKEN,
} }
.into(), .into(),
pos, pos,

View File

@ -5,6 +5,7 @@ pub mod expr;
pub mod flags; pub mod flags;
pub mod ident; pub mod ident;
pub mod namespace; pub mod namespace;
pub mod namespace_none;
pub mod script_fn; pub mod script_fn;
pub mod stmt; pub mod stmt;
@ -16,6 +17,8 @@ pub use flags::{ASTFlags, FnAccess};
pub use ident::Ident; pub use ident::Ident;
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub use namespace::Namespace; pub use namespace::Namespace;
#[cfg(feature = "no_module")]
pub use namespace_none::Namespace;
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub use script_fn::EncapsulatedEnviron; pub use script_fn::EncapsulatedEnviron;

22
src/ast/namespace_none.rs Normal file
View File

@ -0,0 +1,22 @@
//! Namespace reference type.
#![cfg(feature = "no_module")]
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
/// _(internals)_ A chain of [module][crate::Module] names to namespace-qualify a variable or function call.
/// Exported under the `internals` feature only.
///
/// Not available under `no_module`.
#[derive(Debug, Clone, Eq, PartialEq, Default, Hash)]
pub struct Namespace;
impl Namespace {
/// Constant for no namespace.
pub const NONE: Self = Self;
#[inline(always)]
pub const fn is_empty(&self) -> bool {
true
}
}

View File

@ -16,7 +16,6 @@ use std::{fmt, hash::Hash};
/// ///
/// Not available under `no_module` or `no_function`. /// Not available under `no_module` or `no_function`.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct EncapsulatedEnviron { pub struct EncapsulatedEnviron {
/// Functions defined within the same [`AST`][crate::AST]. /// Functions defined within the same [`AST`][crate::AST].
@ -24,7 +23,7 @@ pub struct EncapsulatedEnviron {
/// Imported [modules][crate::Module]. /// Imported [modules][crate::Module].
pub imports: Box<[(ImmutableString, crate::SharedModule)]>, pub imports: Box<[(ImmutableString, crate::SharedModule)]>,
/// Globally-defined constants. /// Globally-defined constants.
pub constants: Option<crate::eval::GlobalConstants>, pub constants: Option<crate::eval::SharedGlobalConstants>,
} }
/// _(internals)_ A type containing information on a script-defined function. /// _(internals)_ A type containing information on a script-defined function.
@ -35,7 +34,6 @@ pub struct ScriptFnDef {
pub body: StmtBlock, pub body: StmtBlock,
/// Encapsulated AST environment, if any. /// Encapsulated AST environment, if any.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
pub environ: Option<crate::Shared<EncapsulatedEnviron>>, pub environ: Option<crate::Shared<EncapsulatedEnviron>>,
/// Function name. /// Function name.
pub name: ImmutableString, pub name: ImmutableString,
@ -51,12 +49,14 @@ pub struct ScriptFnDef {
/// ///
/// Block doc-comments are kept in a single string slice with line-breaks within. /// Block doc-comments are kept in a single string slice with line-breaks within.
/// ///
/// Line doc-comments are kept in one string slice per line without the termination line-break. /// Line doc-comments are merged, with line-breaks, into a single string slice without a termination line-break.
/// ///
/// Leading white-spaces are stripped, and each string slice always starts with the /// Leading white-spaces are stripped, and each string slice always starts with the
/// corresponding doc-comment leader: `///` or `/**`. /// corresponding doc-comment leader: `///` or `/**`.
///
/// Each line in non-block doc-comments starts with `///`.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
pub comments: Box<[Box<str>]>, pub comments: Box<[crate::Identifier]>,
} }
impl fmt::Display for ScriptFnDef { impl fmt::Display for ScriptFnDef {
@ -100,10 +100,12 @@ pub struct ScriptFnMetadata<'a> {
/// ///
/// Block doc-comments are kept in a single string slice with line-breaks within. /// Block doc-comments are kept in a single string slice with line-breaks within.
/// ///
/// Line doc-comments are kept in one string slice per line without the termination line-break. /// Line doc-comments are merged, with line-breaks, into a single string slice without a termination line-break.
/// ///
/// Leading white-spaces are stripped, and each string slice always starts with the /// Leading white-spaces are stripped, and each string slice always starts with the
/// corresponding doc-comment leader: `///` or `/**`. /// corresponding doc-comment leader: `///` or `/**`.
///
/// Each line in non-block doc-comments starts with `///`.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
pub comments: Vec<&'a str>, pub comments: Vec<&'a str>,
} }

View File

@ -2,7 +2,8 @@
use super::{ASTFlags, ASTNode, BinaryExpr, Expr, FnCallExpr, Ident}; use super::{ASTFlags, ASTNode, BinaryExpr, Expr, FnCallExpr, Ident};
use crate::engine::KEYWORD_EVAL; use crate::engine::KEYWORD_EVAL;
use crate::tokenizer::{Span, Token}; use crate::tokenizer::Token;
use crate::types::Span;
use crate::{calc_fn_hash, Position, StaticVec, INT}; use crate::{calc_fn_hash, Position, StaticVec, INT};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -61,10 +62,8 @@ impl OpAssignment {
#[must_use] #[must_use]
#[inline(always)] #[inline(always)]
pub fn new_op_assignment(name: &str, pos: Position) -> Self { pub fn new_op_assignment(name: &str, pos: Position) -> Self {
Self::new_op_assignment_from_token( let op = Token::lookup_symbol_from_syntax(name).expect("operator");
&Token::lookup_symbol_from_syntax(name).expect("operator"), Self::new_op_assignment_from_token(op, pos)
pos,
)
} }
/// Create a new [`OpAssignment`] from a [`Token`]. /// Create a new [`OpAssignment`] from a [`Token`].
/// ///
@ -72,10 +71,11 @@ impl OpAssignment {
/// ///
/// Panics if the token is not an op-assignment operator. /// Panics if the token is not an op-assignment operator.
#[must_use] #[must_use]
pub fn new_op_assignment_from_token(op: &Token, pos: Position) -> Self { pub fn new_op_assignment_from_token(op: Token, pos: Position) -> Self {
let op_raw = op let op_raw = op
.get_base_op_from_assignment() .get_base_op_from_assignment()
.expect("op-assignment operator"); .expect("op-assignment operator");
Self { Self {
hash_op_assign: calc_fn_hash(None, op.literal_syntax(), 2), hash_op_assign: calc_fn_hash(None, op.literal_syntax(), 2),
hash_op: calc_fn_hash(None, op_raw.literal_syntax(), 2), hash_op: calc_fn_hash(None, op_raw.literal_syntax(), 2),
@ -92,10 +92,8 @@ impl OpAssignment {
#[must_use] #[must_use]
#[inline(always)] #[inline(always)]
pub fn new_op_assignment_from_base(name: &str, pos: Position) -> Self { pub fn new_op_assignment_from_base(name: &str, pos: Position) -> Self {
Self::new_op_assignment_from_base_token( let op = Token::lookup_symbol_from_syntax(name).expect("operator");
&Token::lookup_symbol_from_syntax(name).expect("operator"), Self::new_op_assignment_from_base_token(op, pos)
pos,
)
} }
/// Convert a [`Token`] into a new [`OpAssignment`]. /// Convert a [`Token`] into a new [`OpAssignment`].
/// ///
@ -104,8 +102,8 @@ impl OpAssignment {
/// Panics if the token is cannot be converted into an op-assignment operator. /// Panics if the token is cannot be converted into an op-assignment operator.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn new_op_assignment_from_base_token(op: &Token, pos: Position) -> Self { pub fn new_op_assignment_from_base_token(op: Token, pos: Position) -> Self {
Self::new_op_assignment_from_token(&op.convert_to_op_assignment().expect("operator"), pos) Self::new_op_assignment_from_token(op.convert_to_op_assignment().expect("operator"), pos)
} }
} }

View File

@ -100,17 +100,19 @@ fn print_error(input: &str, mut err: EvalAltResult) {
// Print error position // Print error position
if pos.is_none() { if pos.is_none() {
// No position // No position
println!("{err}"); println!("\x1b[31m{err}\x1b[39m");
} else { } else {
// Specific position - print line text // Specific position - print line text
println!("{line_no}{}", lines[pos.line().unwrap() - 1]); println!("{line_no}{}", lines[pos.line().unwrap() - 1]);
// Display position marker for (i, err_line) in err.to_string().split('\n').enumerate() {
println!( // Display position marker
"{0:>1$} {err}", println!(
"^", "\x1b[31m{0:>1$}{err_line}\x1b[39m",
line_no.len() + pos.position().unwrap(), if i > 0 { "| " } else { "^ " },
); line_no.len() + pos.position().unwrap() + 1,
);
}
} }
} }
@ -237,7 +239,13 @@ fn debug_callback(
) -> Result<DebuggerCommand, Box<EvalAltResult>> { ) -> Result<DebuggerCommand, Box<EvalAltResult>> {
// Check event // Check event
match event { match event {
DebuggerEvent::Start if source.is_some() => {
println!("\x1b[32m! Script '{}' start\x1b[39m", source.unwrap())
}
DebuggerEvent::Start => println!("\x1b[32m! Script start\x1b[39m"), DebuggerEvent::Start => println!("\x1b[32m! Script start\x1b[39m"),
DebuggerEvent::End if source.is_some() => {
println!("\x1b[31m! Script '{}' end\x1b[39m", source.unwrap())
}
DebuggerEvent::End => println!("\x1b[31m! Script end\x1b[39m"), DebuggerEvent::End => println!("\x1b[31m! Script end\x1b[39m"),
DebuggerEvent::Step => (), DebuggerEvent::Step => (),
DebuggerEvent::BreakPoint(n) => { DebuggerEvent::BreakPoint(n) => {
@ -572,7 +580,7 @@ fn debug_callback(
break Err(EvalAltResult::ErrorRuntime(msg.trim().into(), pos).into()); break Err(EvalAltResult::ErrorRuntime(msg.trim().into(), pos).into());
} }
["run" | "r"] => { ["run" | "r"] => {
println!("Restarting script..."); println!("Terminating current run...");
break Err(EvalAltResult::ErrorTerminated(Dynamic::UNIT, pos).into()); break Err(EvalAltResult::ErrorTerminated(Dynamic::UNIT, pos).into());
} }
_ => eprintln!( _ => eprintln!(
@ -604,7 +612,10 @@ fn main() {
#[allow(deprecated)] #[allow(deprecated)]
engine.register_debugger( engine.register_debugger(
// Store the current source in the debugger state // Store the current source in the debugger state
|_| "".into(), |engine, mut debugger| {
debugger.set_state(engine.const_empty_string());
debugger
},
// Main debugging interface // Main debugging interface
move |context, event, node, source, pos| { move |context, event, node, source, pos| {
debug_callback(context, event, node, source, pos, &lines) debug_callback(context, event, node, source, pos, &lines)
@ -627,10 +638,13 @@ fn main() {
while let Err(err) = engine.run_ast_with_scope(&mut Scope::new(), &ast) { while let Err(err) = engine.run_ast_with_scope(&mut Scope::new(), &ast) {
match *err { match *err {
// Loop back to restart // Loop back to restart
EvalAltResult::ErrorTerminated(..) => (), EvalAltResult::ErrorTerminated(..) => {
println!("Restarting script...");
}
// Break evaluation // Break evaluation
_ => { _ => {
print_error(&script, *err); print_error(&script, *err);
println!();
break; break;
} }
} }

View File

@ -31,12 +31,14 @@ fn print_error(input: &str, mut err: EvalAltResult) {
// Specific position - print line text // Specific position - print line text
println!("{line_no}{}", lines[pos.line().unwrap() - 1]); println!("{line_no}{}", lines[pos.line().unwrap() - 1]);
// Display position marker for (i, err_line) in err.to_string().split('\n').enumerate() {
println!( // Display position marker
"{0:>1$} {err}", println!(
"^", "{0:>1$}{err_line}",
line_no.len() + pos.position().unwrap(), if i > 0 { "| " } else { "^ " },
); line_no.len() + pos.position().unwrap() + 1,
);
}
} }
} }
@ -245,6 +247,7 @@ fn setup_editor() -> Editor<()> {
rl rl
} }
/// Module containing sample functions.
#[export_module] #[export_module]
mod sample_functions { mod sample_functions {
/// This is a sample function. /// This is a sample function.

View File

@ -8,11 +8,15 @@ fn eprint_error(input: &str, mut err: EvalAltResult) {
let line_no = format!("{line}: "); let line_no = format!("{line}: ");
eprintln!("{line_no}{}", lines[line - 1]); eprintln!("{line_no}{}", lines[line - 1]);
eprintln!(
"{:>1$} {err_msg}", for (i, err_line) in err_msg.to_string().split('\n').enumerate() {
"^", // Display position marker
line_no.len() + pos.position().unwrap(), println!(
); "{0:>1$}{err_line}",
if i > 0 { "| " } else { "^ " },
line_no.len() + pos.position().unwrap() + 1,
);
}
eprintln!(); eprintln!();
} }

View File

@ -147,7 +147,7 @@ pub struct Engine {
/// Callback closure for debugging. /// Callback closure for debugging.
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
pub(crate) debugger: Option< pub(crate) debugger_interface: Option<
Box<( Box<(
Box<crate::eval::OnDebuggingInit>, Box<crate::eval::OnDebuggingInit>,
Box<crate::eval::OnDebuggerCallback>, Box<crate::eval::OnDebuggerCallback>,
@ -191,6 +191,9 @@ impl fmt::Debug for Engine {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
f.field("limits", &self.limits); f.field("limits", &self.limits);
#[cfg(feature = "debugging")]
f.field("debugger_interface", &self.debugger_interface.is_some());
f.finish() f.finish()
} }
} }
@ -305,7 +308,7 @@ impl Engine {
limits: crate::api::limits::Limits::new(), limits: crate::api::limits::Limits::new(),
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
debugger: None, debugger_interface: None,
}; };
// Add the global namespace module // Add the global namespace module
@ -348,4 +351,12 @@ impl Engine {
pub fn const_empty_string(&self) -> ImmutableString { pub fn const_empty_string(&self) -> ImmutableString {
self.get_interned_string("") self.get_interned_string("")
} }
/// Is there a debugger interface registered with this [`Engine`]?
#[cfg(feature = "debugging")]
#[inline(always)]
#[must_use]
pub(crate) const fn is_debugger_registered(&self) -> bool {
self.debugger_interface.is_some()
}
} }

View File

@ -5,8 +5,8 @@ use super::{Caches, GlobalRuntimeState, Target};
use crate::ast::{ASTFlags, Expr, OpAssignment}; use crate::ast::{ASTFlags, Expr, OpAssignment};
use crate::config::hashing::SusLock; use crate::config::hashing::SusLock;
use crate::engine::{FN_IDX_GET, FN_IDX_SET}; use crate::engine::{FN_IDX_GET, FN_IDX_SET};
use crate::tokenizer::NO_TOKEN;
use crate::types::dynamic::Union; use crate::types::dynamic::Union;
use crate::types::RestoreOnDrop;
use crate::{ use crate::{
calc_fn_hash, Dynamic, Engine, FnArgsVec, Position, RhaiResult, RhaiResultOf, Scope, ERR, calc_fn_hash, Dynamic, Engine, FnArgsVec, Position, RhaiResult, RhaiResultOf, Scope, ERR,
}; };
@ -68,21 +68,13 @@ impl Engine {
idx: &mut Dynamic, idx: &mut Dynamic,
pos: Position, pos: Position,
) -> RhaiResultOf<Dynamic> { ) -> RhaiResultOf<Dynamic> {
let orig_level = global.level; auto_restore! { let orig_level = global.level; global.level += 1 }
global.level += 1;
let global = &mut *RestoreOnDrop::lock(global, move |g| g.level = orig_level);
self.exec_native_fn_call( let hash = hash_idx().0;
global, let args = &mut [target, idx];
caches,
FN_IDX_GET, self.exec_native_fn_call(global, caches, FN_IDX_GET, NO_TOKEN, hash, args, true, pos)
None, .map(|(r, ..)| r)
hash_idx().0,
&mut [target, idx],
true,
pos,
)
.map(|(r, ..)| r)
} }
/// Call a set indexer. /// Call a set indexer.
@ -97,19 +89,13 @@ impl Engine {
is_ref_mut: bool, is_ref_mut: bool,
pos: Position, pos: Position,
) -> RhaiResultOf<(Dynamic, bool)> { ) -> RhaiResultOf<(Dynamic, bool)> {
let orig_level = global.level; auto_restore! { let orig_level = global.level; global.level += 1 }
global.level += 1;
let global = &mut *RestoreOnDrop::lock(global, move |g| g.level = orig_level); let hash = hash_idx().1;
let args = &mut [target, idx, new_val];
self.exec_native_fn_call( self.exec_native_fn_call(
global, global, caches, FN_IDX_SET, NO_TOKEN, hash, args, is_ref_mut, pos,
caches,
FN_IDX_SET,
None,
hash_idx().1,
&mut [target, idx, new_val],
is_ref_mut,
pos,
) )
} }
@ -599,13 +585,15 @@ impl Engine {
// Try to call index setter if value is changed // Try to call index setter if value is changed
let idx = &mut idx_val_for_setter; let idx = &mut idx_val_for_setter;
let new_val = &mut new_val; let new_val = &mut new_val;
self.call_indexer_set( // The return value of a indexer setter (usually `()`) is thrown away and not used.
global, caches, target, idx, new_val, is_ref_mut, op_pos, let _ = self
) .call_indexer_set(
.or_else(|e| match *e { global, caches, target, idx, new_val, is_ref_mut, op_pos,
ERR::ErrorIndexingType(..) => Ok((Dynamic::UNIT, false)), )
_ => Err(e), .or_else(|e| match *e {
})?; ERR::ErrorIndexingType(..) => Ok((Dynamic::UNIT, false)),
_ => Err(e),
})?;
} }
Ok(result) Ok(result)
@ -659,8 +647,8 @@ impl Engine {
// Try to call index setter // Try to call index setter
let new_val = &mut new_val; let new_val = &mut new_val;
// The return value of a indexer setter (usually `()`) is thrown away and not used.
self.call_indexer_set( let _ = self.call_indexer_set(
global, caches, target, idx_val, new_val, is_ref_mut, op_pos, global, caches, target, idx_val, new_val, is_ref_mut, op_pos,
)?; )?;
} }
@ -696,19 +684,17 @@ impl Engine {
let reset = let reset =
self.run_debugger_with_reset(global, caches, scope, this_ptr, rhs)?; self.run_debugger_with_reset(global, caches, scope, this_ptr, rhs)?;
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
let global = auto_restore!(global if reset.is_some() => move |g| g.debugger_mut().reset_status(reset));
&mut *RestoreOnDrop::lock_if(reset.is_some(), global, move |g| {
g.debugger_mut().reset_status(reset)
});
let crate::ast::FnCallExpr { let crate::ast::FnCallExpr {
name, hashes, args, .. name, hashes, args, ..
} = &**x; } = &**x;
// Truncate the index values upon exit // Truncate the index values upon exit
let offset = idx_values.len() - args.len(); auto_restore! {
let idx_values = idx_values => truncate;
&mut *RestoreOnDrop::lock(idx_values, move |v| v.truncate(offset)); let offset = idx_values.len() - args.len();
}
let call_args = &mut idx_values[offset..]; let call_args = &mut idx_values[offset..];
let arg1_pos = args.get(0).map_or(Position::NONE, Expr::position); let arg1_pos = args.get(0).map_or(Position::NONE, Expr::position);
@ -764,9 +750,11 @@ impl Engine {
if op_info.is_op_assignment() { if op_info.is_op_assignment() {
let args = &mut [target.as_mut()]; let args = &mut [target.as_mut()];
let (mut orig_val, ..) = self let (mut orig_val, ..) = self
.exec_native_fn_call( .exec_native_fn_call(
global, caches, getter, None, *hash_get, args, is_ref_mut, *pos, global, caches, getter, NO_TOKEN, *hash_get, args, is_ref_mut,
*pos,
) )
.or_else(|err| match *err { .or_else(|err| match *err {
// Try an indexer if property does not exist // Try an indexer if property does not exist
@ -798,8 +786,9 @@ impl Engine {
} }
let args = &mut [target.as_mut(), &mut new_val]; let args = &mut [target.as_mut(), &mut new_val];
self.exec_native_fn_call( self.exec_native_fn_call(
global, caches, setter, None, *hash_set, args, is_ref_mut, *pos, global, caches, setter, NO_TOKEN, *hash_set, args, is_ref_mut, *pos,
) )
.or_else(|err| match *err { .or_else(|err| match *err {
// Try an indexer if property does not exist // Try an indexer if property does not exist
@ -824,8 +813,9 @@ impl Engine {
let ((getter, hash_get), _, name) = &**x; let ((getter, hash_get), _, name) = &**x;
let args = &mut [target.as_mut()]; let args = &mut [target.as_mut()];
self.exec_native_fn_call( self.exec_native_fn_call(
global, caches, getter, None, *hash_get, args, is_ref_mut, *pos, global, caches, getter, NO_TOKEN, *hash_get, args, is_ref_mut, *pos,
) )
.map_or_else( .map_or_else(
|err| match *err { |err| match *err {
@ -866,21 +856,17 @@ impl Engine {
global, caches, scope, this_ptr, _node, global, caches, scope, this_ptr, _node,
)?; )?;
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
let global = &mut *RestoreOnDrop::lock_if( auto_restore!(global if reset.is_some() => move |g| g.debugger_mut().reset_status(reset));
reset.is_some(),
global,
move |g| g.debugger_mut().reset_status(reset),
);
let crate::ast::FnCallExpr { let crate::ast::FnCallExpr {
name, hashes, args, .. name, hashes, args, ..
} = &**x; } = &**x;
// Truncate the index values upon exit // Truncate the index values upon exit
let offset = idx_values.len() - args.len(); auto_restore! {
let idx_values = &mut *RestoreOnDrop::lock(idx_values, move |v| { idx_values => truncate;
v.truncate(offset) let offset = idx_values.len() - args.len();
}); }
let call_args = &mut idx_values[offset..]; let call_args = &mut idx_values[offset..];
let arg1_pos = args.get(0).map_or(Position::NONE, Expr::position); let arg1_pos = args.get(0).map_or(Position::NONE, Expr::position);
@ -915,14 +901,13 @@ impl Engine {
self.run_debugger(global, caches, scope, this_ptr, _node)?; self.run_debugger(global, caches, scope, this_ptr, _node)?;
let ((getter, hash_get), (setter, hash_set), name) = &**p; let ((getter, hash_get), (setter, hash_set), name) = &**p;
let mut arg_values = [target.as_mut(), &mut Dynamic::UNIT.clone()]; let args = &mut [target.as_mut()];
let args = &mut arg_values[..1];
// Assume getters are always pure // Assume getters are always pure
let (mut val, ..) = self let (mut val, ..) = self
.exec_native_fn_call( .exec_native_fn_call(
global, caches, getter, None, *hash_get, args, is_ref_mut, global, caches, getter, NO_TOKEN, *hash_get, args,
pos, is_ref_mut, pos,
) )
.or_else(|err| match *err { .or_else(|err| match *err {
// Try an indexer if property does not exist // Try an indexer if property does not exist
@ -952,14 +937,15 @@ impl Engine {
// Feed the value back via a setter just in case it has been updated // Feed the value back via a setter just in case it has been updated
if may_be_changed { if may_be_changed {
// Re-use args because the first &mut parameter will not be consumed // Re-use args because the first &mut parameter will not be consumed
let mut arg_values = [target.as_mut(), val.as_mut()]; let args = &mut [target.as_mut(), val.as_mut()];
let args = &mut arg_values;
self.exec_native_fn_call( // The return value is thrown away and not used.
global, caches, setter, None, *hash_set, args, is_ref_mut, let _ = self
pos, .exec_native_fn_call(
) global, caches, setter, NO_TOKEN, *hash_set, args,
.or_else( is_ref_mut, pos,
|err| match *err { )
.or_else(|err| match *err {
// Try an indexer if property does not exist // Try an indexer if property does not exist
ERR::ErrorDotExpr(..) => { ERR::ErrorDotExpr(..) => {
let idx = &mut name.into(); let idx = &mut name.into();
@ -978,8 +964,7 @@ impl Engine {
}) })
} }
_ => Err(err), _ => Err(err),
}, })?;
)?;
} }
Ok((result, may_be_changed)) Ok((result, may_be_changed))
@ -992,22 +977,17 @@ impl Engine {
global, caches, scope, this_ptr, _node, global, caches, scope, this_ptr, _node,
)?; )?;
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
let global = &mut *RestoreOnDrop::lock_if( auto_restore!(global if reset.is_some() => move |g| g.debugger_mut().reset_status(reset));
reset.is_some(),
global,
move |g| g.debugger_mut().reset_status(reset),
);
let crate::ast::FnCallExpr { let crate::ast::FnCallExpr {
name, hashes, args, .. name, hashes, args, ..
} = &**f; } = &**f;
// Truncate the index values upon exit // Truncate the index values upon exit
let offset = idx_values.len() - args.len(); auto_restore! {
let idx_values = idx_values => truncate;
&mut *RestoreOnDrop::lock(idx_values, move |v| { let offset = idx_values.len() - args.len();
v.truncate(offset) }
});
let call_args = &mut idx_values[offset..]; let call_args = &mut idx_values[offset..];
let pos1 = args.get(0).map_or(Position::NONE, Expr::position); let pos1 = args.get(0).map_or(Position::NONE, Expr::position);

View File

@ -74,13 +74,10 @@ impl Engine {
/// ///
/// [`Position`] in [`EvalAltResult`][crate::EvalAltResult] is always [`NONE`][Position::NONE] /// [`Position`] in [`EvalAltResult`][crate::EvalAltResult] is always [`NONE`][Position::NONE]
/// and should be set afterwards. /// and should be set afterwards.
pub(crate) fn raise_err_if_over_data_size_limit( pub(crate) fn throw_on_size(&self, (_arr, _map, s): (usize, usize, usize)) -> RhaiResultOf<()> {
&self,
(_arr, _map, s): (usize, usize, usize),
) -> RhaiResultOf<()> {
if self if self
.limits .limits
.max_string_size .max_string_len
.map_or(false, |max| s > max.get()) .map_or(false, |max| s > max.get())
{ {
return Err( return Err(
@ -127,9 +124,10 @@ impl Engine {
let sizes = value.borrow().calc_data_sizes(true); let sizes = value.borrow().calc_data_sizes(true);
self.raise_err_if_over_data_size_limit(sizes) self.throw_on_size(sizes)
.map(|_| value) .map_err(|err| err.fill_position(pos))?;
.map_err(|err| err.fill_position(pos))
Ok(value)
} }
/// Raise an error if the size of a [`Dynamic`] is out of limits (if any). /// Raise an error if the size of a [`Dynamic`] is out of limits (if any).

View File

@ -10,10 +10,10 @@ use std::{fmt, iter::repeat, mem};
/// Callback function to initialize the debugger. /// Callback function to initialize the debugger.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type OnDebuggingInit = dyn Fn(&Engine) -> Dynamic; pub type OnDebuggingInit = dyn Fn(&Engine, Debugger) -> Debugger;
/// Callback function to initialize the debugger. /// Callback function to initialize the debugger.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type OnDebuggingInit = dyn Fn(&Engine) -> Dynamic + Send + Sync; pub type OnDebuggingInit = dyn Fn(&Engine, Debugger) -> Debugger + Send + Sync;
/// Callback function for debugging. /// Callback function for debugging.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
@ -268,12 +268,12 @@ impl Debugger {
/// Create a new [`Debugger`]. /// Create a new [`Debugger`].
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub const fn new(status: DebuggerStatus, state: Dynamic) -> Self { pub const fn new(status: DebuggerStatus) -> Self {
Self { Self {
status, status,
break_points: Vec::new(), break_points: Vec::new(),
call_stack: Vec::new(), call_stack: Vec::new(),
state, state: Dynamic::UNIT,
} }
} }
/// Get the current call stack. /// Get the current call stack.
@ -415,7 +415,7 @@ impl Engine {
this_ptr: &mut Dynamic, this_ptr: &mut Dynamic,
node: impl Into<ASTNode<'a>>, node: impl Into<ASTNode<'a>>,
) -> RhaiResultOf<()> { ) -> RhaiResultOf<()> {
if self.debugger.is_some() { if self.is_debugger_registered() {
if let Some(cmd) = if let Some(cmd) =
self.run_debugger_with_reset_raw(global, caches, scope, this_ptr, node)? self.run_debugger_with_reset_raw(global, caches, scope, this_ptr, node)?
{ {
@ -440,7 +440,7 @@ impl Engine {
this_ptr: &mut Dynamic, this_ptr: &mut Dynamic,
node: impl Into<ASTNode<'a>>, node: impl Into<ASTNode<'a>>,
) -> RhaiResultOf<Option<DebuggerStatus>> { ) -> RhaiResultOf<Option<DebuggerStatus>> {
if self.debugger.is_some() { if self.is_debugger_registered() {
self.run_debugger_with_reset_raw(global, caches, scope, this_ptr, node) self.run_debugger_with_reset_raw(global, caches, scope, this_ptr, node)
} else { } else {
Ok(None) Ok(None)
@ -508,11 +508,10 @@ impl Engine {
node: ASTNode<'a>, node: ASTNode<'a>,
event: DebuggerEvent, event: DebuggerEvent,
) -> Result<Option<DebuggerStatus>, Box<crate::EvalAltResult>> { ) -> Result<Option<DebuggerStatus>, Box<crate::EvalAltResult>> {
let src = global.source_raw().cloned(); if let Some(ref x) = self.debugger_interface {
let src = src.as_ref().map(|s| s.as_str()); let src = global.source_raw().cloned();
let context = crate::EvalContext::new(self, global, caches, scope, this_ptr); let src = src.as_ref().map(|s| s.as_str());
let context = EvalContext::new(self, global, caches, scope, this_ptr);
if let Some(ref x) = self.debugger {
let (.., ref on_debugger) = **x; let (.., ref on_debugger) = **x;
let command = on_debugger(context, event, node, src, node.position())?; let command = on_debugger(context, event, node, src, node.position())?;

View File

@ -6,7 +6,6 @@ use crate::{Dynamic, Engine, Scope};
use std::prelude::v1::*; use std::prelude::v1::*;
/// Context of a script evaluation process. /// Context of a script evaluation process.
#[derive(Debug)]
#[allow(dead_code)] #[allow(dead_code)]
pub struct EvalContext<'a, 's, 'ps, 'g, 'c, 't> { pub struct EvalContext<'a, 's, 'ps, 'g, 'c, 't> {
/// The current [`Engine`]. /// The current [`Engine`].

View File

@ -62,25 +62,22 @@ impl Engine {
} }
Expr::Variable(v, None, ..) => match &**v { Expr::Variable(v, None, ..) => match &**v {
// Normal variable access // Normal variable access
#[cfg(not(feature = "no_module"))]
(_, ns, ..) if ns.is_empty() => { (_, ns, ..) if ns.is_empty() => {
self.search_scope_only(global, caches, scope, this_ptr, expr) self.search_scope_only(global, caches, scope, this_ptr, expr)
} }
#[cfg(feature = "no_module")]
(_, (), ..) => self.search_scope_only(global, caches, scope, this_ptr, expr),
// Qualified variable access // Qualified variable access
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
(_, namespace, hash_var, var_name) => { (_, ns, hash_var, var_name) => {
// foo:bar::baz::VARIABLE // foo:bar::baz::VARIABLE
if let Some(module) = self.search_imports(global, namespace) { if let Some(module) = self.search_imports(global, ns) {
return module.get_qualified_var(*hash_var).map_or_else( return module.get_qualified_var(*hash_var).map_or_else(
|| { || {
let sep = crate::tokenizer::Token::DoubleColon.literal_syntax(); let sep = crate::tokenizer::Token::DoubleColon.literal_syntax();
Err(ERR::ErrorVariableNotFound( Err(ERR::ErrorVariableNotFound(
format!("{namespace}{sep}{var_name}"), format!("{ns}{sep}{var_name}"),
namespace.position(), ns.position(),
) )
.into()) .into())
}, },
@ -94,7 +91,7 @@ impl Engine {
// global::VARIABLE // global::VARIABLE
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
if namespace.len() == 1 && namespace.root() == crate::engine::KEYWORD_GLOBAL { if ns.len() == 1 && ns.root() == crate::engine::KEYWORD_GLOBAL {
if let Some(ref constants) = global.constants { if let Some(ref constants) = global.constants {
if let Some(value) = if let Some(value) =
crate::func::locked_write(constants).get_mut(var_name.as_str()) crate::func::locked_write(constants).get_mut(var_name.as_str())
@ -109,17 +106,17 @@ impl Engine {
let sep = crate::tokenizer::Token::DoubleColon.literal_syntax(); let sep = crate::tokenizer::Token::DoubleColon.literal_syntax();
return Err(ERR::ErrorVariableNotFound( return Err(ERR::ErrorVariableNotFound(
format!("{namespace}{sep}{var_name}"), format!("{ns}{sep}{var_name}"),
namespace.position(), ns.position(),
) )
.into()); .into());
} }
Err( Err(ERR::ErrorModuleNotFound(ns.to_string(), ns.position()).into())
ERR::ErrorModuleNotFound(namespace.to_string(), namespace.position())
.into(),
)
} }
#[cfg(feature = "no_module")]
_ => unreachable!("Invalid expression {:?}", expr),
}, },
_ => unreachable!("Expr::Variable expected but gets {:?}", expr), _ => unreachable!("Expr::Variable expected but gets {:?}", expr),
} }
@ -142,14 +139,18 @@ impl Engine {
let index = match expr { let index = match expr {
// Check if the variable is `this` // Check if the variable is `this`
Expr::Variable(v, None, ..) if v.0.is_none() && v.3 == KEYWORD_THIS => { Expr::Variable(v, None, ..)
if v.0.is_none() && v.1.is_empty() && v.3 == KEYWORD_THIS =>
{
return if this_ptr.is_null() { return if this_ptr.is_null() {
Err(ERR::ErrorUnboundThis(expr.position()).into()) Err(ERR::ErrorUnboundThis(expr.position()).into())
} else { } else {
Ok(this_ptr.into()) Ok(this_ptr.into())
}; };
} }
_ if global.always_search_scope => 0, _ if global.always_search_scope => 0,
Expr::Variable(_, Some(i), ..) => i.get() as usize, Expr::Variable(_, Some(i), ..) => i.get() as usize,
// Scripted function with the same name // Scripted function with the same name
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
@ -165,6 +166,7 @@ impl Engine {
return Ok(val.into()); return Ok(val.into());
} }
Expr::Variable(v, None, ..) => v.0.map_or(0, NonZeroUsize::get), Expr::Variable(v, None, ..) => v.0.map_or(0, NonZeroUsize::get),
_ => unreachable!("Expr::Variable expected but gets {:?}", expr), _ => unreachable!("Expr::Variable expected but gets {:?}", expr),
}; };
@ -232,10 +234,7 @@ impl Engine {
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
let reset = self.run_debugger_with_reset(global, caches, scope, this_ptr, expr)?; let reset = self.run_debugger_with_reset(global, caches, scope, this_ptr, expr)?;
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
let global = auto_restore!(global if reset.is_some() => move |g| g.debugger_mut().reset_status(reset));
&mut *crate::types::RestoreOnDrop::lock_if(reset.is_some(), global, move |g| {
g.debugger_mut().reset_status(reset)
});
self.track_operation(global, expr.position())?; self.track_operation(global, expr.position())?;
@ -266,10 +265,7 @@ impl Engine {
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
let reset = self.run_debugger_with_reset(global, caches, scope, this_ptr, expr)?; let reset = self.run_debugger_with_reset(global, caches, scope, this_ptr, expr)?;
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
let global = auto_restore!(global if reset.is_some() => move |g| g.debugger_mut().reset_status(reset));
&mut *crate::types::RestoreOnDrop::lock_if(reset.is_some(), global, move |g| {
g.debugger_mut().reset_status(reset)
});
self.track_operation(global, expr.position())?; self.track_operation(global, expr.position())?;
@ -327,7 +323,7 @@ impl Engine {
total_data_sizes.1 + val_sizes.1, total_data_sizes.1 + val_sizes.1,
total_data_sizes.2 + val_sizes.2, total_data_sizes.2 + val_sizes.2,
); );
self.raise_err_if_over_data_size_limit(total_data_sizes) self.throw_on_size(total_data_sizes)
.map_err(|err| err.fill_position(item_expr.position()))?; .map_err(|err| err.fill_position(item_expr.position()))?;
} }
@ -358,7 +354,7 @@ impl Engine {
total_data_sizes.1 + delta.1, total_data_sizes.1 + delta.1,
total_data_sizes.2 + delta.2, total_data_sizes.2 + delta.2,
); );
self.raise_err_if_over_data_size_limit(total_data_sizes) self.throw_on_size(total_data_sizes)
.map_err(|err| err.fill_position(value_expr.position()))?; .map_err(|err| err.fill_position(value_expr.position()))?;
} }

View File

@ -8,7 +8,7 @@ use std::prelude::v1::*;
/// Collection of globally-defined constants. /// Collection of globally-defined constants.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub type GlobalConstants = pub type SharedGlobalConstants =
crate::Shared<crate::Locked<std::collections::BTreeMap<ImmutableString, Dynamic>>>; crate::Shared<crate::Locked<std::collections::BTreeMap<ImmutableString, Dynamic>>>;
/// _(internals)_ Global runtime states. /// _(internals)_ Global runtime states.
@ -67,12 +67,12 @@ pub struct GlobalRuntimeState {
/// Interior mutability is needed because it is shared in order to aid in cloning. /// Interior mutability is needed because it is shared in order to aid in cloning.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub constants: Option<GlobalConstants>, pub constants: Option<SharedGlobalConstants>,
/// Custom state that can be used by the external host. /// Custom state that can be used by the external host.
pub tag: Dynamic, pub tag: Dynamic,
/// Debugging interface. /// Debugging interface.
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
pub(crate) debugger: Option<super::Debugger>, pub(crate) debugger: Option<Box<super::Debugger>>,
} }
impl GlobalRuntimeState { impl GlobalRuntimeState {
@ -103,8 +103,9 @@ impl GlobalRuntimeState {
tag: engine.default_tag().clone(), tag: engine.default_tag().clone(),
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
debugger: engine.debugger.as_ref().map(|x| { debugger: engine.debugger_interface.as_ref().map(|x| {
crate::eval::Debugger::new(crate::eval::DebuggerStatus::Init, (x.0)(engine)) let dbg = crate::eval::Debugger::new(crate::eval::DebuggerStatus::Init);
(x.0)(engine, dbg).into()
}), }),
} }
} }
@ -115,7 +116,7 @@ impl GlobalRuntimeState {
#[inline] #[inline]
#[must_use] #[must_use]
pub fn num_imports(&self) -> usize { pub fn num_imports(&self) -> usize {
self.modules.as_ref().map_or(0, |m| m.len()) self.modules.as_deref().map_or(0, crate::StaticVec::len)
} }
/// Get the globally-imported [module][crate::Module] at a particular index. /// Get the globally-imported [module][crate::Module] at a particular index.
/// ///
@ -138,7 +139,7 @@ impl GlobalRuntimeState {
&mut self, &mut self,
index: usize, index: usize,
) -> Option<&mut crate::SharedModule> { ) -> Option<&mut crate::SharedModule> {
self.modules.as_mut().and_then(|m| m.get_mut(index)) self.modules.as_deref_mut().and_then(|m| m.get_mut(index))
} }
/// Get the index of a globally-imported [module][crate::Module] by name. /// Get the index of a globally-imported [module][crate::Module] by name.
/// ///
@ -183,8 +184,8 @@ impl GlobalRuntimeState {
self.imports = None; self.imports = None;
self.modules = None; self.modules = None;
} else if self.imports.is_some() { } else if self.imports.is_some() {
self.imports.as_mut().unwrap().truncate(size); self.imports.as_deref_mut().unwrap().truncate(size);
self.modules.as_mut().unwrap().truncate(size); self.modules.as_deref_mut().unwrap().truncate(size);
} }
} }
/// Get an iterator to the stack of globally-imported [modules][crate::Module] in reverse order. /// Get an iterator to the stack of globally-imported [modules][crate::Module] in reverse order.
@ -194,10 +195,11 @@ impl GlobalRuntimeState {
#[inline] #[inline]
pub fn iter_imports(&self) -> impl Iterator<Item = (&str, &crate::Module)> { pub fn iter_imports(&self) -> impl Iterator<Item = (&str, &crate::Module)> {
self.imports self.imports
.iter() .as_deref()
.flat_map(|x| x.iter()) .into_iter()
.flatten()
.rev() .rev()
.zip(self.modules.iter().flat_map(|x| x.iter()).rev()) .zip(self.modules.as_deref().into_iter().flatten().rev())
.map(|(name, module)| (name.as_str(), &**module)) .map(|(name, module)| (name.as_str(), &**module))
} }
/// Get an iterator to the stack of globally-imported [modules][crate::Module] in reverse order. /// Get an iterator to the stack of globally-imported [modules][crate::Module] in reverse order.
@ -209,10 +211,11 @@ impl GlobalRuntimeState {
&self, &self,
) -> impl Iterator<Item = (&ImmutableString, &crate::SharedModule)> { ) -> impl Iterator<Item = (&ImmutableString, &crate::SharedModule)> {
self.imports self.imports
.iter() .as_deref()
.flat_map(|x| x.iter()) .into_iter()
.flatten()
.rev() .rev()
.zip(self.modules.iter().flat_map(|x| x.iter()).rev()) .zip(self.modules.as_deref().into_iter().flatten())
} }
/// Get an iterator to the stack of globally-imported [modules][crate::Module] in forward order. /// Get an iterator to the stack of globally-imported [modules][crate::Module] in forward order.
/// ///
@ -223,9 +226,10 @@ impl GlobalRuntimeState {
&self, &self,
) -> impl Iterator<Item = (&ImmutableString, &crate::SharedModule)> { ) -> impl Iterator<Item = (&ImmutableString, &crate::SharedModule)> {
self.imports self.imports
.iter() .as_deref()
.flat_map(|x| x.iter()) .into_iter()
.zip(self.modules.iter().flat_map(|x| x.iter())) .flatten()
.zip(self.modules.as_deref().into_iter().flatten())
} }
/// Can the particular function with [`Dynamic`] parameter(s) exist in the stack of /// Can the particular function with [`Dynamic`] parameter(s) exist in the stack of
/// globally-imported [modules][crate::Module]? /// globally-imported [modules][crate::Module]?
@ -234,7 +238,7 @@ impl GlobalRuntimeState {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline] #[inline]
pub(crate) fn may_contain_dynamic_fn(&self, hash_script: u64) -> bool { pub(crate) fn may_contain_dynamic_fn(&self, hash_script: u64) -> bool {
self.modules.as_ref().map_or(false, |m| { self.modules.as_deref().map_or(false, |m| {
m.iter().any(|m| m.may_contain_dynamic_fn(hash_script)) m.iter().any(|m| m.may_contain_dynamic_fn(hash_script))
}) })
} }
@ -323,7 +327,7 @@ impl GlobalRuntimeState {
/// Panics if the debugging interface is not set. /// Panics if the debugging interface is not set.
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
pub fn debugger_mut(&mut self) -> &mut super::Debugger { pub fn debugger_mut(&mut self) -> &mut super::Debugger {
self.debugger.as_mut().unwrap() self.debugger.as_deref_mut().unwrap()
} }
} }

View File

@ -17,10 +17,10 @@ pub use debugger::{
OnDebuggerCallback, OnDebuggingInit, OnDebuggerCallback, OnDebuggingInit,
}; };
pub use eval_context::EvalContext; pub use eval_context::EvalContext;
pub use global_state::GlobalRuntimeState;
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub use global_state::GlobalConstants; pub use global_state::SharedGlobalConstants;
pub use global_state::GlobalRuntimeState;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub use target::calc_offset_len; pub use target::calc_offset_len;
pub use target::{calc_index, Target}; pub use target::{calc_index, Target};

View File

@ -3,16 +3,28 @@
use super::{Caches, EvalContext, GlobalRuntimeState, Target}; use super::{Caches, EvalContext, GlobalRuntimeState, Target};
use crate::api::events::VarDefInfo; use crate::api::events::VarDefInfo;
use crate::ast::{ use crate::ast::{
ASTFlags, BinaryExpr, Expr, Ident, OpAssignment, Stmt, SwitchCasesCollection, TryCatchBlock, ASTFlags, BinaryExpr, Expr, OpAssignment, Stmt, SwitchCasesCollection, TryCatchBlock,
}; };
use crate::func::{get_builtin_op_assignment_fn, get_hasher}; use crate::func::{get_builtin_op_assignment_fn, get_hasher};
use crate::types::dynamic::AccessMode; use crate::types::dynamic::{AccessMode, Union};
use crate::types::RestoreOnDrop;
use crate::{Dynamic, Engine, RhaiResult, RhaiResultOf, Scope, ERR, INT}; use crate::{Dynamic, Engine, RhaiResult, RhaiResultOf, Scope, ERR, INT};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
impl Dynamic {
/// If the value is a string, intern it.
#[inline(always)]
fn intern_string(self, engine: &Engine) -> Self {
match self.0 {
Union::Str(..) => engine
.get_interned_string(self.into_immutable_string().expect("`ImmutableString`"))
.into(),
_ => self,
}
}
}
impl Engine { impl Engine {
/// Evaluate a statements block. /// Evaluate a statements block.
pub(crate) fn eval_stmt_block( pub(crate) fn eval_stmt_block(
@ -29,10 +41,10 @@ impl Engine {
} }
// Restore scope at end of block if necessary // Restore scope at end of block if necessary
let orig_scope_len = scope.len(); auto_restore! {
let scope = &mut *RestoreOnDrop::lock_if(restore_orig_state, scope, move |s| { scope if restore_orig_state => rewind;
s.rewind(orig_scope_len); let orig_scope_len = scope.len();
}); }
// Restore global state at end of block if necessary // Restore global state at end of block if necessary
let orig_always_search_scope = global.always_search_scope; let orig_always_search_scope = global.always_search_scope;
@ -43,7 +55,7 @@ impl Engine {
global.scope_level += 1; global.scope_level += 1;
} }
let global = &mut *RestoreOnDrop::lock_if(restore_orig_state, global, move |g| { auto_restore!(global if restore_orig_state => move |g| {
g.scope_level -= 1; g.scope_level -= 1;
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -55,10 +67,10 @@ impl Engine {
}); });
// Pop new function resolution caches at end of block // Pop new function resolution caches at end of block
let orig_fn_resolution_caches_len = caches.fn_resolution_caches_len(); auto_restore! {
let caches = &mut *RestoreOnDrop::lock(caches, move |c| { caches => rewind_fn_resolution_caches;
c.rewind_fn_resolution_caches(orig_fn_resolution_caches_len) let orig_fn_resolution_caches_len = caches.fn_resolution_caches_len();
}); }
// Run the statements // Run the statements
statements.iter().try_fold(Dynamic::UNIT, |_, stmt| { statements.iter().try_fold(Dynamic::UNIT, |_, stmt| {
@ -129,23 +141,25 @@ impl Engine {
let args = &mut [&mut *lock_guard, &mut new_val]; let args = &mut [&mut *lock_guard, &mut new_val];
if self.fast_operators() { if self.fast_operators() {
if let Some(func) = get_builtin_op_assignment_fn(op_assign_token, args[0], args[1]) if let Some((func, ctx)) =
get_builtin_op_assignment_fn(op_assign_token.clone(), args[0], args[1])
{ {
// Built-in found // Built-in found
let op = op_assign_token.literal_syntax(); let op = op_assign_token.literal_syntax();
auto_restore! { let orig_level = global.level; global.level += 1 }
let orig_level = global.level; let context = if ctx {
global.level += 1; Some((self, op, None, &*global, *op_pos).into())
let global = &*RestoreOnDrop::lock(global, move |g| g.level = orig_level); } else {
None
let context = (self, op, None, global, *op_pos).into(); };
return func(context, args).map(|_| ()); return func(context, args).map(|_| ());
} }
} }
let op_assign = op_assign_token.literal_syntax(); let op_assign = op_assign_token.literal_syntax();
let op = op_token.literal_syntax(); let op = op_token.literal_syntax();
let token = Some(op_assign_token); let token = op_assign_token.clone();
match self match self
.exec_native_fn_call(global, caches, op_assign, token, hash, args, true, *op_pos) .exec_native_fn_call(global, caches, op_assign, token, hash, args, true, *op_pos)
@ -154,7 +168,7 @@ impl Engine {
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_assign)) =>
{ {
// Expand to `var = var op rhs` // Expand to `var = var op rhs`
let token = Some(op_token); let token = op_token.clone();
*args[0] = self *args[0] = self
.exec_native_fn_call( .exec_native_fn_call(
@ -168,14 +182,14 @@ impl Engine {
self.check_data_size(&*args[0], root.position())?; self.check_data_size(&*args[0], root.position())?;
} else { } else {
// Normal assignment // Normal assignment
match target {
// If value is a string, intern it // Lock it again just in case it is shared
if new_val.is_string() { Target::RefMut(_) | Target::TempValue(_) => {
let value = new_val.into_immutable_string().expect("`ImmutableString`"); *target.write_lock::<Dynamic>().unwrap() = new_val
new_val = self.get_interned_string(value).into(); }
#[allow(unreachable_patterns)]
_ => **target = new_val,
} }
*target.write_lock::<Dynamic>().unwrap() = new_val;
} }
target.propagate_changed_value(op_info.pos) target.propagate_changed_value(op_info.pos)
@ -194,9 +208,7 @@ impl Engine {
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
let reset = self.run_debugger_with_reset(global, caches, scope, this_ptr, stmt)?; let reset = self.run_debugger_with_reset(global, caches, scope, this_ptr, stmt)?;
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
let global = &mut *RestoreOnDrop::lock_if(reset.is_some(), global, move |g| { auto_restore!(global if reset.is_some() => move |g| g.debugger_mut().reset_status(reset));
g.debugger_mut().reset_status(reset)
});
// Coded this way for better branch prediction. // Coded this way for better branch prediction.
// Popular branches are lifted out of the `match` statement into their own branches. // Popular branches are lifted out of the `match` statement into their own branches.
@ -223,14 +235,13 @@ impl Engine {
let mut target = self.search_namespace(global, caches, scope, this_ptr, lhs)?; let mut target = self.search_namespace(global, caches, scope, this_ptr, lhs)?;
let is_temp_result = !target.is_ref();
let var_name = x.3.as_str(); let var_name = x.3.as_str();
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
// Also handle case where target is a `Dynamic` shared value // Also handle case where target is a `Dynamic` shared value
// (returned by a variable resolver, for example) // (returned by a variable resolver, for example)
let is_temp_result = !target.is_ref() && !target.is_shared(); let is_temp_result = is_temp_result && !target.is_shared();
#[cfg(feature = "no_closure")]
let is_temp_result = !target.is_ref();
// Cannot assign to temp result from expression // Cannot assign to temp result from expression
if is_temp_result { if is_temp_result {
@ -250,36 +261,31 @@ impl Engine {
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
{ {
let mut rhs_val = self let rhs_val = self
.eval_expr(global, caches, scope, this_ptr, rhs)? .eval_expr(global, caches, scope, this_ptr, rhs)?
.flatten(); .flatten()
.intern_string(self);
// If value is a string, intern it
if rhs_val.is_string() {
let value = rhs_val.into_immutable_string().expect("`ImmutableString`");
rhs_val = self.get_interned_string(value).into();
}
let _new_val = Some((rhs_val, op_info)); let _new_val = Some((rhs_val, op_info));
// Must be either `var[index] op= val` or `var.prop op= val` // Must be either `var[index] op= val` or `var.prop op= val`.
match lhs { // The return value of any op-assignment (should be `()`) is thrown away and not used.
// name op= rhs (handled above) let _ =
Expr::Variable(..) => { match lhs {
unreachable!("Expr::Variable case is already handled") // name op= rhs (handled above)
} Expr::Variable(..) => {
// idx_lhs[idx_expr] op= rhs unreachable!("Expr::Variable case is already handled")
#[cfg(not(feature = "no_index"))] }
Expr::Index(..) => { // idx_lhs[idx_expr] op= rhs
self.eval_dot_index_chain(global, caches, scope, this_ptr, lhs, _new_val) #[cfg(not(feature = "no_index"))]
} Expr::Index(..) => self
// dot_lhs.dot_rhs op= rhs .eval_dot_index_chain(global, caches, scope, this_ptr, lhs, _new_val),
#[cfg(not(feature = "no_object"))] // dot_lhs.dot_rhs op= rhs
Expr::Dot(..) => { #[cfg(not(feature = "no_object"))]
self.eval_dot_index_chain(global, caches, scope, this_ptr, lhs, _new_val) Expr::Dot(..) => self
} .eval_dot_index_chain(global, caches, scope, this_ptr, lhs, _new_val),
_ => unreachable!("cannot assign to expression: {:?}", lhs), _ => unreachable!("cannot assign to expression: {:?}", lhs),
}?; }?;
return Ok(Dynamic::UNIT); return Ok(Dynamic::UNIT);
} }
@ -494,27 +500,29 @@ impl Engine {
// 2) Global modules - packages // 2) Global modules - packages
// 3) Imported modules - functions marked with global namespace // 3) Imported modules - functions marked with global namespace
// 4) Global sub-modules - functions marked with global namespace // 4) Global sub-modules - functions marked with global namespace
let func = self let iter_func = self
.global_modules .global_modules
.iter() .iter()
.find_map(|m| m.get_iter(iter_type)); .find_map(|m| m.get_iter(iter_type));
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
let func = func.or_else(|| global.get_iter(iter_type)).or_else(|| { let iter_func = iter_func
self.global_sub_modules .or_else(|| global.get_iter(iter_type))
.iter() .or_else(|| {
.flat_map(|m| m.values()) self.global_sub_modules
.find_map(|m| m.get_qualified_iter(iter_type)) .as_deref()
}); .into_iter()
.flatten()
.find_map(|(_, m)| m.get_qualified_iter(iter_type))
});
let func = func.ok_or_else(|| ERR::ErrorFor(expr.start_position()))?; let iter_func = iter_func.ok_or_else(|| ERR::ErrorFor(expr.start_position()))?;
// Restore scope at end of statement // Restore scope at end of statement
let orig_scope_len = scope.len(); auto_restore! {
let scope = &mut *RestoreOnDrop::lock(scope, move |s| { scope => rewind;
s.rewind(orig_scope_len); let orig_scope_len = scope.len();
}); }
// Add the loop variables // Add the loop variables
let counter_index = if counter.is_empty() { let counter_index = if counter.is_empty() {
usize::MAX usize::MAX
@ -528,7 +536,7 @@ impl Engine {
let mut result = Dynamic::UNIT; let mut result = Dynamic::UNIT;
for (x, iter_value) in func(iter_obj).enumerate() { for (x, iter_value) in iter_func(iter_obj).enumerate() {
// Increment counter // Increment counter
if counter_index < usize::MAX { if counter_index < usize::MAX {
// As the variable increments from 0, this should always work // As the variable increments from 0, this should always work
@ -596,10 +604,7 @@ impl Engine {
Stmt::TryCatch(x, ..) => { Stmt::TryCatch(x, ..) => {
let TryCatchBlock { let TryCatchBlock {
try_block, try_block,
catch_var: catch_var,
Ident {
name: catch_var, ..
},
catch_block, catch_block,
} = &**x; } = &**x;
@ -644,14 +649,13 @@ impl Engine {
}; };
// Restore scope at end of block // Restore scope at end of block
let orig_scope_len = scope.len(); auto_restore! {
let scope = scope if !catch_var.is_empty() => rewind;
&mut *RestoreOnDrop::lock_if(!catch_var.is_empty(), scope, move |s| { let orig_scope_len = scope.len();
s.rewind(orig_scope_len); }
});
if !catch_var.is_empty() { if !catch_var.is_empty() {
scope.push(catch_var.clone(), err_value); scope.push(catch_var.name.clone(), err_value);
} }
self.eval_stmt_block(global, caches, scope, this_ptr, catch_block, true) self.eval_stmt_block(global, caches, scope, this_ptr, catch_block, true)
@ -721,7 +725,8 @@ impl Engine {
// Evaluate initial value // Evaluate initial value
let mut value = self let mut value = self
.eval_expr(global, caches, scope, this_ptr, expr)? .eval_expr(global, caches, scope, this_ptr, expr)?
.flatten(); .flatten()
.intern_string(self);
let _alias = if !rewind_scope { let _alias = if !rewind_scope {
// Put global constants into global module // Put global constants into global module
@ -759,7 +764,7 @@ impl Engine {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
if let Some(alias) = _alias { if let Some(alias) = _alias {
scope.add_alias_by_index(scope.len() - 1, alias.name.as_str().into()); scope.add_alias_by_index(scope.len() - 1, alias.as_str().into());
} }
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
@ -826,6 +831,7 @@ impl Engine {
// Export statement // Export statement
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Stmt::Export(x, ..) => { Stmt::Export(x, ..) => {
use crate::ast::Ident;
let (Ident { name, pos, .. }, Ident { name: alias, .. }) = &**x; let (Ident { name, pos, .. }, Ident { name: alias, .. }) = &**x;
// Mark scope variables as public // Mark scope variables as public
scope.search(name).map_or_else( scope.search(name).map_or_else(

View File

@ -81,6 +81,7 @@ pub fn calc_index<E>(
/// A type that encapsulates a mutation target for an expression with side effects. /// A type that encapsulates a mutation target for an expression with side effects.
#[derive(Debug)] #[derive(Debug)]
#[must_use]
pub enum Target<'a> { pub enum Target<'a> {
/// The target is a mutable reference to a [`Dynamic`]. /// The target is a mutable reference to a [`Dynamic`].
RefMut(&'a mut Dynamic), RefMut(&'a mut Dynamic),
@ -88,9 +89,9 @@ pub enum Target<'a> {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
SharedValue { SharedValue {
/// Lock guard to the shared [`Dynamic`]. /// Lock guard to the shared [`Dynamic`].
source: crate::types::dynamic::DynamicWriteLock<'a, Dynamic>, guard: crate::types::dynamic::DynamicWriteLock<'a, Dynamic>,
/// Copy of the value. /// Copy of the shared value.
value: Dynamic, shared_value: Dynamic,
}, },
/// The target is a temporary [`Dynamic`] value (i.e. its mutation can cause no side effects). /// The target is a temporary [`Dynamic`] value (i.e. its mutation can cause no side effects).
TempValue(Dynamic), TempValue(Dynamic),
@ -177,13 +178,12 @@ impl<'a> Target<'a> {
} }
} }
/// Is the [`Target`] a shared value? /// Is the [`Target`] a shared value?
#[cfg(not(feature = "no_closure"))]
#[inline] #[inline]
#[must_use] #[must_use]
pub fn is_shared(&self) -> bool { pub fn is_shared(&self) -> bool {
match self { #[cfg(not(feature = "no_closure"))]
return match self {
Self::RefMut(r) => r.is_shared(), Self::RefMut(r) => r.is_shared(),
#[cfg(not(feature = "no_closure"))]
Self::SharedValue { .. } => true, Self::SharedValue { .. } => true,
Self::TempValue(value) => value.is_shared(), Self::TempValue(value) => value.is_shared(),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -191,16 +191,17 @@ impl<'a> Target<'a> {
| Self::BitField { .. } | Self::BitField { .. }
| Self::BlobByte { .. } | Self::BlobByte { .. }
| Self::StringChar { .. } => false, | Self::StringChar { .. } => false,
} };
#[cfg(feature = "no_closure")]
return false;
} }
/// Get the value of the [`Target`] as a [`Dynamic`], cloning a referenced value if necessary. /// Get the value of the [`Target`] as a [`Dynamic`], cloning a referenced value if necessary.
#[inline] #[inline]
#[must_use]
pub fn take_or_clone(self) -> Dynamic { pub fn take_or_clone(self) -> Dynamic {
match self { match self {
Self::RefMut(r) => r.clone(), // Referenced value is cloned Self::RefMut(r) => r.clone(), // Referenced value is cloned
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Self::SharedValue { value, .. } => value, // Original shared value is simply taken Self::SharedValue { shared_value, .. } => shared_value, // Original shared value is simply taken
Self::TempValue(value) => value, // Owned value is simply taken Self::TempValue(value) => value, // Owned value is simply taken
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Self::Bit { value, .. } => value, // boolean is taken Self::Bit { value, .. } => value, // boolean is taken
@ -223,12 +224,11 @@ impl<'a> Target<'a> {
} }
/// Convert a shared or reference [`Target`] into a target with an owned value. /// Convert a shared or reference [`Target`] into a target with an owned value.
#[inline(always)] #[inline(always)]
#[must_use]
pub fn into_owned(self) -> Self { pub fn into_owned(self) -> Self {
match self { match self {
Self::RefMut(r) => Self::TempValue(r.clone()), Self::RefMut(r) => Self::TempValue(r.clone()),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Self::SharedValue { value, .. } => Self::TempValue(value), Self::SharedValue { shared_value, .. } => Self::TempValue(shared_value),
_ => self, _ => self,
} }
} }
@ -240,7 +240,7 @@ impl<'a> Target<'a> {
match self { match self {
Self::RefMut(r) => r, Self::RefMut(r) => r,
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Self::SharedValue { source, .. } => source, Self::SharedValue { guard, .. } => guard,
Self::TempValue(value) => value, Self::TempValue(value) => value,
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Self::Bit { source, .. } => source, Self::Bit { source, .. } => source,
@ -366,9 +366,12 @@ impl<'a> From<&'a mut Dynamic> for Target<'a> {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
if value.is_shared() { if value.is_shared() {
// Cloning is cheap for a shared value // Cloning is cheap for a shared value
let val = value.clone(); let shared_value = value.clone();
let source = value.write_lock::<Dynamic>().expect("`Dynamic`"); let guard = value.write_lock::<Dynamic>().expect("`Dynamic`");
return Self::SharedValue { source, value: val }; return Self::SharedValue {
guard,
shared_value,
};
} }
Self::RefMut(value) Self::RefMut(value)
@ -383,7 +386,7 @@ impl Deref for Target<'_> {
match self { match self {
Self::RefMut(r) => r, Self::RefMut(r) => r,
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Self::SharedValue { source, .. } => source, Self::SharedValue { guard, .. } => guard,
Self::TempValue(ref value) => value, Self::TempValue(ref value) => value,
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Self::Bit { ref value, .. } Self::Bit { ref value, .. }
@ -416,7 +419,7 @@ impl DerefMut for Target<'_> {
match self { match self {
Self::RefMut(r) => r, Self::RefMut(r) => r,
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Self::SharedValue { source, .. } => &mut *source, Self::SharedValue { guard, .. } => &mut *guard,
Self::TempValue(ref mut value) => value, Self::TempValue(ref mut value) => value,
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Self::Bit { ref mut value, .. } Self::Bit { ref mut value, .. }
@ -437,7 +440,6 @@ impl AsMut<Dynamic> for Target<'_> {
impl<T: Into<Dynamic>> From<T> for Target<'_> { impl<T: Into<Dynamic>> From<T> for Target<'_> {
#[inline(always)] #[inline(always)]
#[must_use]
fn from(value: T) -> Self { fn from(value: T) -> Self {
Self::TempValue(value.into()) Self::TempValue(value.into())
} }

View File

@ -24,8 +24,8 @@ use num_traits::Float;
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
use rust_decimal::Decimal; use rust_decimal::Decimal;
/// The message: data type was checked /// The `unchecked` feature is not active.
const BUILTIN: &str = "data type was checked"; const CHECKED_BUILD: bool = cfg!(not(feature = "unchecked"));
/// Is the type a numeric type? /// Is the type a numeric type?
#[inline] #[inline]
@ -71,12 +71,12 @@ fn is_numeric(type_id: TypeId) -> bool {
/// A function that returns `true`. /// A function that returns `true`.
#[inline(always)] #[inline(always)]
fn const_true_fn(_: NativeCallContext, _: &mut [&mut Dynamic]) -> RhaiResult { fn const_true_fn(_: Option<NativeCallContext>, _: &mut [&mut Dynamic]) -> RhaiResult {
Ok(Dynamic::TRUE) Ok(Dynamic::TRUE)
} }
/// A function that returns `false`. /// A function that returns `false`.
#[inline(always)] #[inline(always)]
fn const_false_fn(_: NativeCallContext, _: &mut [&mut Dynamic]) -> RhaiResult { fn const_false_fn(_: Option<NativeCallContext>, _: &mut [&mut Dynamic]) -> RhaiResult {
Ok(Dynamic::FALSE) Ok(Dynamic::FALSE)
} }
@ -84,60 +84,60 @@ fn const_false_fn(_: NativeCallContext, _: &mut [&mut Dynamic]) -> RhaiResult {
/// ///
/// The return function will be registered as a _method_, so the first parameter cannot be consumed. /// The return function will be registered as a _method_, so the first parameter cannot be consumed.
#[must_use] #[must_use]
pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option<FnBuiltin> { pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<FnBuiltin> {
let type1 = x.type_id(); let type1 = x.type_id();
let type2 = y.type_id(); let type2 = y.type_id();
macro_rules! impl_op { macro_rules! impl_op {
($xx:ident $op:tt $yy:ident) => { |_, args| { ($xx:ident $op:tt $yy:ident) => { (|_, args| {
let x = &*args[0].read_lock::<$xx>().expect(BUILTIN); let x = &*args[0].read_lock::<$xx>().unwrap();
let y = &*args[1].read_lock::<$yy>().expect(BUILTIN); let y = &*args[1].read_lock::<$yy>().unwrap();
Ok((x $op y).into()) Ok((x $op y).into())
} }; }, false) };
($xx:ident . $func:ident ( $yy:ty )) => { |_, args| { ($xx:ident . $func:ident ( $yy:ty )) => { (|_, args| {
let x = &*args[0].read_lock::<$xx>().expect(BUILTIN); let x = &*args[0].read_lock::<$xx>().unwrap();
let y = &*args[1].read_lock::<$yy>().expect(BUILTIN); let y = &*args[1].read_lock::<$yy>().unwrap();
Ok(x.$func(y).into()) Ok(x.$func(y).into())
} }; }, false) };
($xx:ident . $func:ident ( $yy:ident . $yyy:ident () )) => { |_, args| { ($xx:ident . $func:ident ( $yy:ident . $yyy:ident () )) => { (|_, args| {
let x = &*args[0].read_lock::<$xx>().expect(BUILTIN); let x = &*args[0].read_lock::<$xx>().unwrap();
let y = &*args[1].read_lock::<$yy>().expect(BUILTIN); let y = &*args[1].read_lock::<$yy>().unwrap();
Ok(x.$func(y.$yyy()).into()) Ok(x.$func(y.$yyy()).into())
} }; }, false) };
($func:ident ( $op:tt )) => { |_, args| { ($func:ident ( $op:tt )) => { (|_, args| {
let (x, y) = $func(args); let (x, y) = $func(args);
Ok((x $op y).into()) Ok((x $op y).into())
} }; }, false) };
($base:ty => $xx:ident $op:tt $yy:ident) => { |_, args| { ($base:ty => $xx:ident $op:tt $yy:ident) => { (|_, args| {
let x = args[0].$xx().expect(BUILTIN) as $base; let x = args[0].$xx().unwrap() as $base;
let y = args[1].$yy().expect(BUILTIN) as $base; let y = args[1].$yy().unwrap() as $base;
Ok((x $op y).into()) Ok((x $op y).into())
} }; }, false) };
($base:ty => $xx:ident . $func:ident ( $yy:ident as $yyy:ty)) => { |_, args| { ($base:ty => $xx:ident . $func:ident ( $yy:ident as $yyy:ty)) => { (|_, args| {
let x = args[0].$xx().expect(BUILTIN) as $base; let x = args[0].$xx().unwrap() as $base;
let y = args[1].$yy().expect(BUILTIN) as $base; let y = args[1].$yy().unwrap() as $base;
Ok(x.$func(y as $yyy).into()) Ok(x.$func(y as $yyy).into())
} }; }, false) };
($base:ty => $func:ident ( $xx:ident, $yy:ident )) => { |_, args| { ($base:ty => $func:ident ( $xx:ident, $yy:ident )) => { (|_, args| {
let x = args[0].$xx().expect(BUILTIN) as $base; let x = args[0].$xx().unwrap() as $base;
let y = args[1].$yy().expect(BUILTIN) as $base; let y = args[1].$yy().unwrap() as $base;
$func(x, y).map(Into::into) $func(x, y).map(Into::into)
} }; }, false) };
(from $base:ty => $xx:ident $op:tt $yy:ident) => { |_, args| { (from $base:ty => $xx:ident $op:tt $yy:ident) => { (|_, args| {
let x = <$base>::from(args[0].$xx().expect(BUILTIN)); let x = <$base>::from(args[0].$xx().unwrap());
let y = <$base>::from(args[1].$yy().expect(BUILTIN)); let y = <$base>::from(args[1].$yy().unwrap());
Ok((x $op y).into()) Ok((x $op y).into())
} }; }, false) };
(from $base:ty => $xx:ident . $func:ident ( $yy:ident )) => { |_, args| { (from $base:ty => $xx:ident . $func:ident ( $yy:ident )) => { (|_, args| {
let x = <$base>::from(args[0].$xx().expect(BUILTIN)); let x = <$base>::from(args[0].$xx().unwrap());
let y = <$base>::from(args[1].$yy().expect(BUILTIN)); let y = <$base>::from(args[1].$yy().unwrap());
Ok(x.$func(y).into()) Ok(x.$func(y).into())
} }; }, false) };
(from $base:ty => $func:ident ( $xx:ident, $yy:ident )) => { |_, args| { (from $base:ty => $func:ident ( $xx:ident, $yy:ident )) => { (|_, args| {
let x = <$base>::from(args[0].$xx().expect(BUILTIN)); let x = <$base>::from(args[0].$xx().unwrap());
let y = <$base>::from(args[1].$yy().expect(BUILTIN)); let y = <$base>::from(args[1].$yy().unwrap());
$func(x, y).map(Into::into) $func(x, y).map(Into::into)
} }; }, false) };
} }
// Check for common patterns // Check for common patterns
@ -183,16 +183,8 @@ pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option<
Ampersand => Some(impl_op!(INT => as_int & as_int)), Ampersand => Some(impl_op!(INT => as_int & as_int)),
Pipe => Some(impl_op!(INT => as_int | as_int)), Pipe => Some(impl_op!(INT => as_int | as_int)),
XOr => Some(impl_op!(INT => as_int ^ as_int)), XOr => Some(impl_op!(INT => as_int ^ as_int)),
ExclusiveRange => Some(|_, args| { ExclusiveRange => Some(impl_op!(INT => as_int .. as_int)),
let x = args[0].as_int().expect(BUILTIN); InclusiveRange => Some(impl_op!(INT => as_int ..= as_int)),
let y = args[1].as_int().expect(BUILTIN);
Ok((x..y).into())
}),
InclusiveRange => Some(|_, args| {
let x = args[0].as_int().expect(BUILTIN);
let y = args[1].as_int().expect(BUILTIN);
Ok((x..=y).into())
}),
_ => None, _ => None,
}; };
} }
@ -214,19 +206,20 @@ pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option<
if type1 == TypeId::of::<ImmutableString>() { if type1 == TypeId::of::<ImmutableString>() {
return match op { return match op {
Plus => Some(|_ctx, args| { Plus => Some((
let s1 = &*args[0].read_lock::<ImmutableString>().expect(BUILTIN); |_ctx, args| {
let s2 = &*args[1].read_lock::<ImmutableString>().expect(BUILTIN); let s1 = &*args[0].read_lock::<ImmutableString>().unwrap();
let s2 = &*args[1].read_lock::<ImmutableString>().unwrap();
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if !s1.is_empty() && !s2.is_empty() { _ctx.unwrap()
let total_len = s1.len() + s2.len(); .engine()
_ctx.engine() .throw_on_size((0, 0, s1.len() + s2.len()))?;
.raise_err_if_over_data_size_limit((0, 0, total_len))?;
}
Ok((s1 + s2).into()) Ok((s1 + s2).into())
}), },
CHECKED_BUILD,
)),
Minus => Some(impl_op!(ImmutableString - ImmutableString)), Minus => Some(impl_op!(ImmutableString - ImmutableString)),
EqualsTo => Some(impl_op!(ImmutableString == ImmutableString)), EqualsTo => Some(impl_op!(ImmutableString == ImmutableString)),
NotEqualsTo => Some(impl_op!(ImmutableString != ImmutableString)), NotEqualsTo => Some(impl_op!(ImmutableString != ImmutableString)),
@ -240,20 +233,22 @@ pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option<
if type1 == TypeId::of::<char>() { if type1 == TypeId::of::<char>() {
return match op { return match op {
Plus => Some(|_ctx, args| { Plus => Some((
let x = args[0].as_char().expect(BUILTIN); |_ctx, args| {
let y = args[1].as_char().expect(BUILTIN); let x = args[0].as_char().unwrap();
let y = args[1].as_char().unwrap();
let mut result = SmartString::new_const(); let mut result = SmartString::new_const();
result.push(x); result.push(x);
result.push(y); result.push(y);
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
_ctx.engine() _ctx.unwrap().engine().throw_on_size((0, 0, result.len()))?;
.raise_err_if_over_data_size_limit((0, 0, result.len()))?;
Ok(result.into()) Ok(result.into())
}), },
CHECKED_BUILD,
)),
EqualsTo => Some(impl_op!(char => as_char == as_char)), EqualsTo => Some(impl_op!(char => as_char == as_char)),
NotEqualsTo => Some(impl_op!(char => as_char != as_char)), NotEqualsTo => Some(impl_op!(char => as_char != as_char)),
GreaterThan => Some(impl_op!(char => as_char > as_char)), GreaterThan => Some(impl_op!(char => as_char > as_char)),
@ -269,27 +264,28 @@ pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option<
use crate::Blob; use crate::Blob;
return match op { return match op {
Plus => Some(|_ctx, args| { Plus => Some((
let blob1 = &*args[0].read_lock::<Blob>().expect(BUILTIN); |_ctx, args| {
let blob2 = &*args[1].read_lock::<Blob>().expect(BUILTIN); let b2 = &*args[1].read_lock::<Blob>().unwrap();
if b2.is_empty() {
return Ok(args[0].flatten_clone());
}
let b1 = &*args[0].read_lock::<Blob>().unwrap();
if b1.is_empty() {
return Ok(args[1].flatten_clone());
}
Ok(Dynamic::from_blob(if blob2.is_empty() {
blob1.clone()
} else if blob1.is_empty() {
blob2.clone()
} else {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
_ctx.engine().raise_err_if_over_data_size_limit(( _ctx.unwrap()
blob1.len() + blob2.len(), .engine()
0, .throw_on_size((b1.len() + b2.len(), 0, 0))?;
0,
))?;
let mut blob = blob1.clone(); let mut blob = b1.clone();
blob.extend(blob2); blob.extend(b2);
blob Ok(Dynamic::from_blob(blob))
})) },
}), CHECKED_BUILD,
)),
EqualsTo => Some(impl_op!(Blob == Blob)), EqualsTo => Some(impl_op!(Blob == Blob)),
NotEqualsTo => Some(impl_op!(Blob != Blob)), NotEqualsTo => Some(impl_op!(Blob != Blob)),
_ => None, _ => None,
@ -298,9 +294,9 @@ pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option<
if type1 == TypeId::of::<()>() { if type1 == TypeId::of::<()>() {
return match op { return match op {
EqualsTo => Some(const_true_fn), EqualsTo => Some((const_true_fn, false)),
NotEqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => { NotEqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => {
Some(const_false_fn) Some((const_false_fn, false))
} }
_ => None, _ => None,
}; };
@ -393,8 +389,8 @@ pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option<
// char op string // char op string
if (type1, type2) == (TypeId::of::<char>(), TypeId::of::<ImmutableString>()) { if (type1, type2) == (TypeId::of::<char>(), TypeId::of::<ImmutableString>()) {
fn get_s1s2(args: &FnCallArgs) -> ([char; 2], [char; 2]) { fn get_s1s2(args: &FnCallArgs) -> ([char; 2], [char; 2]) {
let x = args[0].as_char().expect(BUILTIN); let x = args[0].as_char().unwrap();
let y = &*args[1].read_lock::<ImmutableString>().expect(BUILTIN); let y = &*args[1].read_lock::<ImmutableString>().unwrap();
let s1 = [x, '\0']; let s1 = [x, '\0'];
let mut y = y.chars(); let mut y = y.chars();
let s2 = [y.next().unwrap_or('\0'), y.next().unwrap_or('\0')]; let s2 = [y.next().unwrap_or('\0'), y.next().unwrap_or('\0')];
@ -402,20 +398,22 @@ pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option<
} }
return match op { return match op {
Plus => Some(|_ctx, args| { Plus => Some((
let x = args[0].as_char().expect(BUILTIN); |_ctx, args| {
let y = &*args[1].read_lock::<ImmutableString>().expect(BUILTIN); let x = args[0].as_char().unwrap();
let y = &*args[1].read_lock::<ImmutableString>().unwrap();
let mut result = SmartString::new_const(); let mut result = SmartString::new_const();
result.push(x); result.push(x);
result.push_str(y); result.push_str(y);
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
_ctx.engine() _ctx.unwrap().engine().throw_on_size((0, 0, result.len()))?;
.raise_err_if_over_data_size_limit((0, 0, result.len()))?;
Ok(result.into()) Ok(result.into())
}), },
CHECKED_BUILD,
)),
EqualsTo => Some(impl_op!(get_s1s2(==))), EqualsTo => Some(impl_op!(get_s1s2(==))),
NotEqualsTo => Some(impl_op!(get_s1s2(!=))), NotEqualsTo => Some(impl_op!(get_s1s2(!=))),
GreaterThan => Some(impl_op!(get_s1s2(>))), GreaterThan => Some(impl_op!(get_s1s2(>))),
@ -428,8 +426,8 @@ pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option<
// string op char // string op char
if (type1, type2) == (TypeId::of::<ImmutableString>(), TypeId::of::<char>()) { if (type1, type2) == (TypeId::of::<ImmutableString>(), TypeId::of::<char>()) {
fn get_s1s2(args: &FnCallArgs) -> ([char; 2], [char; 2]) { fn get_s1s2(args: &FnCallArgs) -> ([char; 2], [char; 2]) {
let x = &*args[0].read_lock::<ImmutableString>().expect(BUILTIN); let x = &*args[0].read_lock::<ImmutableString>().unwrap();
let y = args[1].as_char().expect(BUILTIN); let y = args[1].as_char().unwrap();
let mut x = x.chars(); let mut x = x.chars();
let s1 = [x.next().unwrap_or('\0'), x.next().unwrap_or('\0')]; let s1 = [x.next().unwrap_or('\0'), x.next().unwrap_or('\0')];
let s2 = [y, '\0']; let s2 = [y, '\0'];
@ -437,22 +435,27 @@ pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option<
} }
return match op { return match op {
Plus => Some(|_ctx, args| { Plus => Some((
let x = &*args[0].read_lock::<ImmutableString>().expect(BUILTIN); |_ctx, args| {
let y = args[1].as_char().expect(BUILTIN); let x = &*args[0].read_lock::<ImmutableString>().unwrap();
let result = x + y; let y = args[1].as_char().unwrap();
let result = x + y;
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
_ctx.engine() _ctx.unwrap().engine().throw_on_size((0, 0, result.len()))?;
.raise_err_if_over_data_size_limit((0, 0, result.len()))?;
Ok(result.into()) Ok(result.into())
}), },
Minus => Some(|_, args| { CHECKED_BUILD,
let x = &*args[0].read_lock::<ImmutableString>().expect(BUILTIN); )),
let y = args[1].as_char().expect(BUILTIN); Minus => Some((
Ok((x - y).into()) |_, args| {
}), let x = &*args[0].read_lock::<ImmutableString>().unwrap();
let y = args[1].as_char().unwrap();
Ok((x - y).into())
},
false,
)),
EqualsTo => Some(impl_op!(get_s1s2(==))), EqualsTo => Some(impl_op!(get_s1s2(==))),
NotEqualsTo => Some(impl_op!(get_s1s2(!=))), NotEqualsTo => Some(impl_op!(get_s1s2(!=))),
GreaterThan => Some(impl_op!(get_s1s2(>))), GreaterThan => Some(impl_op!(get_s1s2(>))),
@ -465,22 +468,22 @@ pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option<
// () op string // () op string
if (type1, type2) == (TypeId::of::<()>(), TypeId::of::<ImmutableString>()) { if (type1, type2) == (TypeId::of::<()>(), TypeId::of::<ImmutableString>()) {
return match op { return match op {
Plus => Some(|_, args| Ok(args[1].clone())), Plus => Some((|_, args| Ok(args[1].clone()), false)),
EqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => { EqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => {
Some(const_false_fn) Some((const_false_fn, false))
} }
NotEqualsTo => Some(const_true_fn), NotEqualsTo => Some((const_true_fn, false)),
_ => None, _ => None,
}; };
} }
// string op () // string op ()
if (type1, type2) == (TypeId::of::<ImmutableString>(), TypeId::of::<()>()) { if (type1, type2) == (TypeId::of::<ImmutableString>(), TypeId::of::<()>()) {
return match op { return match op {
Plus => Some(|_, args| Ok(args[0].clone())), Plus => Some((|_, args| Ok(args[0].clone()), false)),
EqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => { EqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => {
Some(const_false_fn) Some((const_false_fn, false))
} }
NotEqualsTo => Some(const_true_fn), NotEqualsTo => Some((const_true_fn, false)),
_ => None, _ => None,
}; };
} }
@ -492,21 +495,22 @@ pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option<
if type2 == TypeId::of::<char>() { if type2 == TypeId::of::<char>() {
return match op { return match op {
Plus => Some(|_ctx, args| { Plus => Some((
let mut blob = args[0].read_lock::<Blob>().expect(BUILTIN).clone(); |_ctx, args| {
let mut buf = [0_u8; 4]; let mut blob = args[0].read_lock::<Blob>().unwrap().clone();
let x = args[1].as_char().expect(BUILTIN).encode_utf8(&mut buf); let mut buf = [0_u8; 4];
let x = args[1].as_char().unwrap().encode_utf8(&mut buf);
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
_ctx.engine().raise_err_if_over_data_size_limit(( _ctx.unwrap()
blob.len() + x.len(), .engine()
0, .throw_on_size((blob.len() + x.len(), 0, 0))?;
0,
))?;
blob.extend(x.as_bytes()); blob.extend(x.as_bytes());
Ok(Dynamic::from_blob(blob)) Ok(Dynamic::from_blob(blob))
}), },
CHECKED_BUILD,
)),
_ => None, _ => None,
}; };
} }
@ -525,8 +529,8 @@ pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option<
) )
{ {
return match op { return match op {
NotEqualsTo => Some(const_true_fn), NotEqualsTo => Some((const_true_fn, false)),
Equals => Some(const_false_fn), Equals => Some((const_false_fn, false)),
_ => None, _ => None,
}; };
} }
@ -556,9 +560,9 @@ pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option<
} else if type1 != type2 { } else if type1 != type2 {
// If the types are not the same, default to not compare // If the types are not the same, default to not compare
match op { match op {
NotEqualsTo => Some(const_true_fn), NotEqualsTo => Some((const_true_fn, false)),
EqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => { EqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => {
Some(const_false_fn) Some((const_false_fn, false))
} }
_ => None, _ => None,
} }
@ -571,9 +575,9 @@ pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option<
// Default comparison operators for different types // Default comparison operators for different types
if type2 != type1 { if type2 != type1 {
return match op { return match op {
NotEqualsTo => Some(const_true_fn), NotEqualsTo => Some((const_true_fn, false)),
EqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => { EqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => {
Some(const_false_fn) Some((const_false_fn, false))
} }
_ => None, _ => None,
}; };
@ -587,48 +591,48 @@ pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option<
/// ///
/// The return function is registered as a _method_, so the first parameter cannot be consumed. /// The return function is registered as a _method_, so the first parameter cannot be consumed.
#[must_use] #[must_use]
pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option<FnBuiltin> { pub fn get_builtin_op_assignment_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<FnBuiltin> {
let type1 = x.type_id(); let type1 = x.type_id();
let type2 = y.type_id(); let type2 = y.type_id();
macro_rules! impl_op { macro_rules! impl_op {
($x:ty = x $op:tt $yy:ident) => { |_, args| { ($x:ty = x $op:tt $yy:ident) => { (|_, args| {
let x = args[0].$yy().expect(BUILTIN); let x = args[0].$yy().unwrap();
let y = args[1].$yy().expect(BUILTIN) as $x; let y = args[1].$yy().unwrap() as $x;
Ok((*args[0].write_lock::<$x>().expect(BUILTIN) = x $op y).into()) Ok((*args[0].write_lock::<$x>().unwrap() = x $op y).into())
} }; }, false) };
($x:ident $op:tt $yy:ident) => { |_, args| { ($x:ident $op:tt $yy:ident) => { (|_, args| {
let y = args[1].$yy().expect(BUILTIN) as $x; let y = args[1].$yy().unwrap() as $x;
Ok((*args[0].write_lock::<$x>().expect(BUILTIN) $op y).into()) Ok((*args[0].write_lock::<$x>().unwrap() $op y).into())
} }; }, false) };
($x:ident $op:tt $yy:ident as $yyy:ty) => { |_, args| { ($x:ident $op:tt $yy:ident as $yyy:ty) => { (|_, args| {
let y = args[1].$yy().expect(BUILTIN) as $yyy; let y = args[1].$yy().unwrap() as $yyy;
Ok((*args[0].write_lock::<$x>().expect(BUILTIN) $op y).into()) Ok((*args[0].write_lock::<$x>().unwrap() $op y).into())
} }; }, false) };
($x:ty => $xx:ident . $func:ident ( $yy:ident as $yyy:ty )) => { |_, args| { ($x:ty => $xx:ident . $func:ident ( $yy:ident as $yyy:ty )) => { (|_, args| {
let x = args[0].$xx().expect(BUILTIN); let x = args[0].$xx().unwrap();
let y = args[1].$yy().expect(BUILTIN) as $x; let y = args[1].$yy().unwrap() as $x;
Ok((*args[0].write_lock::<$x>().expect(BUILTIN) = x.$func(y as $yyy)).into()) Ok((*args[0].write_lock::<$x>().unwrap() = x.$func(y as $yyy)).into())
} }; }, false) };
($x:ty => $func:ident ( $xx:ident, $yy:ident )) => { |_, args| { ($x:ty => $func:ident ( $xx:ident, $yy:ident )) => { (|_, args| {
let x = args[0].$xx().expect(BUILTIN); let x = args[0].$xx().unwrap();
let y = args[1].$yy().expect(BUILTIN) as $x; let y = args[1].$yy().unwrap() as $x;
Ok((*args[0].write_lock().expect(BUILTIN) = $func(x, y)?).into()) Ok((*args[0].write_lock().unwrap() = $func(x, y)?).into())
} }; }, false) };
(from $x:ident $op:tt $yy:ident) => { |_, args| { (from $x:ident $op:tt $yy:ident) => { (|_, args| {
let y = <$x>::from(args[1].$yy().expect(BUILTIN)); let y = <$x>::from(args[1].$yy().unwrap());
Ok((*args[0].write_lock::<$x>().expect(BUILTIN) $op y).into()) Ok((*args[0].write_lock::<$x>().unwrap() $op y).into())
} }; }, false) };
(from $x:ty => $xx:ident . $func:ident ( $yy:ident )) => { |_, args| { (from $x:ty => $xx:ident . $func:ident ( $yy:ident )) => { (|_, args| {
let x = args[0].$xx().expect(BUILTIN); let x = args[0].$xx().unwrap();
let y = <$x>::from(args[1].$yy().expect(BUILTIN)); let y = <$x>::from(args[1].$yy().unwrap());
Ok((*args[0].write_lock::<$x>().expect(BUILTIN) = x.$func(y)).into()) Ok((*args[0].write_lock::<$x>().unwrap() = x.$func(y)).into())
} }; }, false) };
(from $x:ty => $func:ident ( $xx:ident, $yy:ident )) => { |_, args| { (from $x:ty => $func:ident ( $xx:ident, $yy:ident )) => { (|_, args| {
let x = args[0].$xx().expect(BUILTIN); let x = args[0].$xx().unwrap();
let y = <$x>::from(args[1].$yy().expect(BUILTIN)); let y = <$x>::from(args[1].$yy().unwrap());
Ok((*args[0].write_lock().expect(BUILTIN) = $func(x, y)?).into()) Ok((*args[0].write_lock().unwrap() = $func(x, y)?).into())
} }; }, false) };
} }
// Check for common patterns // Check for common patterns
@ -682,42 +686,50 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt
if type1 == TypeId::of::<char>() { if type1 == TypeId::of::<char>() {
return match op { return match op {
PlusAssign => Some(|_, args| { PlusAssign => Some((
let y = args[1].as_char().expect(BUILTIN); |_, args| {
let x = &mut *args[0].write_lock::<Dynamic>().expect(BUILTIN); let y = args[1].as_char().unwrap();
let x = &mut *args[0].write_lock::<Dynamic>().unwrap();
let mut buf = SmartString::new_const(); let mut buf = SmartString::new_const();
write!(&mut buf, "{y}").unwrap(); write!(&mut buf, "{y}").unwrap();
buf.push(y); buf.push(y);
Ok((*x = buf.into()).into()) Ok((*x = buf.into()).into())
}), },
false,
)),
_ => None, _ => None,
}; };
} }
if type1 == TypeId::of::<ImmutableString>() { if type1 == TypeId::of::<ImmutableString>() {
return match op { return match op {
PlusAssign => Some(|_ctx, args| { PlusAssign => Some((
let (first, second) = args.split_first_mut().expect(BUILTIN); |_ctx, args| {
let x = &mut *first.write_lock::<ImmutableString>().expect(BUILTIN); let (first, second) = args.split_first_mut().unwrap();
let y = &*second[0].read_lock::<ImmutableString>().expect(BUILTIN); let x = &mut *first.write_lock::<ImmutableString>().unwrap();
let y = &*second[0].read_lock::<ImmutableString>().unwrap();
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if !x.is_empty() && !y.is_empty() { if !x.is_empty() && !y.is_empty() {
let total_len = x.len() + y.len(); let total_len = x.len() + y.len();
_ctx.engine() _ctx.unwrap().engine().throw_on_size((0, 0, total_len))?;
.raise_err_if_over_data_size_limit((0, 0, total_len))?; }
}
Ok((*x += y).into()) Ok((*x += y).into())
}), },
MinusAssign => Some(|_, args| { CHECKED_BUILD,
let (first, second) = args.split_first_mut().expect(BUILTIN); )),
let x = &mut *first.write_lock::<ImmutableString>().expect(BUILTIN); MinusAssign => Some((
let y = &*second[0].read_lock::<ImmutableString>().expect(BUILTIN); |_, args| {
Ok((*x -= y).into()) let (first, second) = args.split_first_mut().unwrap();
}), let x = &mut *first.write_lock::<ImmutableString>().unwrap();
let y = &*second[0].read_lock::<ImmutableString>().unwrap();
Ok((*x -= y).into())
},
false,
)),
_ => None, _ => None,
}; };
} }
@ -729,27 +741,30 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt
use crate::Array; use crate::Array;
return match op { return match op {
PlusAssign => Some(|_ctx, args| { PlusAssign => Some((
let x = std::mem::take(args[1]).into_array().expect(BUILTIN); |_ctx, args| {
let x = std::mem::take(args[1]).into_array().unwrap();
if x.is_empty() { if x.is_empty() {
return Ok(Dynamic::UNIT); return Ok(Dynamic::UNIT);
} }
let _array_is_empty = args[0].read_lock::<Array>().expect(BUILTIN).is_empty(); let _array_is_empty = args[0].read_lock::<Array>().unwrap().is_empty();
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if !_array_is_empty { if !_array_is_empty {
_ctx.engine().check_data_size( _ctx.unwrap().engine().check_data_size(
&*args[0].read_lock().expect(BUILTIN), &*args[0].read_lock().unwrap(),
crate::Position::NONE, crate::Position::NONE,
)?; )?;
} }
let array = &mut *args[0].write_lock::<Array>().expect(BUILTIN); let array = &mut *args[0].write_lock::<Array>().unwrap();
Ok(append(array, x).into()) Ok(append(array, x).into())
}), },
CHECKED_BUILD,
)),
_ => None, _ => None,
}; };
} }
@ -761,19 +776,20 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt
use crate::Blob; use crate::Blob;
return match op { return match op {
PlusAssign => Some(|_ctx, args| { PlusAssign => Some((
let blob2 = std::mem::take(args[1]).into_blob().expect(BUILTIN); |_ctx, args| {
let blob1 = &mut *args[0].write_lock::<Blob>().expect(BUILTIN); let blob2 = std::mem::take(args[1]).into_blob().unwrap();
let blob1 = &mut *args[0].write_lock::<Blob>().unwrap();
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
_ctx.engine().raise_err_if_over_data_size_limit(( _ctx.unwrap()
blob1.len() + blob2.len(), .engine()
0, .throw_on_size((blob1.len() + blob2.len(), 0, 0))?;
0,
))?;
Ok(append(blob1, blob2).into()) Ok(append(blob1, blob2).into())
}), },
CHECKED_BUILD,
)),
_ => None, _ => None,
}; };
} }
@ -847,17 +863,21 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt
// string op= char // string op= char
if (type1, type2) == (TypeId::of::<ImmutableString>(), TypeId::of::<char>()) { if (type1, type2) == (TypeId::of::<ImmutableString>(), TypeId::of::<char>()) {
return match op { return match op {
PlusAssign => Some(|_ctx, args| { PlusAssign => Some((
let mut buf = [0_u8; 4]; |_ctx, args| {
let ch = &*args[1].as_char().expect(BUILTIN).encode_utf8(&mut buf); let mut buf = [0_u8; 4];
let mut x = args[0].write_lock::<ImmutableString>().expect(BUILTIN); let ch = &*args[1].as_char().unwrap().encode_utf8(&mut buf);
let mut x = args[0].write_lock::<ImmutableString>().unwrap();
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
_ctx.engine() _ctx.unwrap()
.raise_err_if_over_data_size_limit((0, 0, x.len() + ch.len()))?; .engine()
.throw_on_size((0, 0, x.len() + ch.len()))?;
Ok((*x += ch).into()) Ok((*x += ch).into())
}), },
CHECKED_BUILD,
)),
MinusAssign => Some(impl_op!(ImmutableString -= as_char as char)), MinusAssign => Some(impl_op!(ImmutableString -= as_char as char)),
_ => None, _ => None,
}; };
@ -865,28 +885,32 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt
// char op= string // char op= string
if (type1, type2) == (TypeId::of::<char>(), TypeId::of::<ImmutableString>()) { if (type1, type2) == (TypeId::of::<char>(), TypeId::of::<ImmutableString>()) {
return match op { return match op {
PlusAssign => Some(|_ctx, args| { PlusAssign => Some((
let ch = { |_ctx, args| {
let s = &*args[1].read_lock::<ImmutableString>().expect(BUILTIN); let ch = {
let s = &*args[1].read_lock::<ImmutableString>().unwrap();
if s.is_empty() { if s.is_empty() {
return Ok(Dynamic::UNIT); return Ok(Dynamic::UNIT);
} }
let mut ch = args[0].as_char().expect(BUILTIN).to_string(); let mut ch = args[0].as_char().unwrap().to_string();
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
_ctx.engine() _ctx.unwrap()
.raise_err_if_over_data_size_limit((0, 0, ch.len() + s.len()))?; .engine()
.throw_on_size((0, 0, ch.len() + s.len()))?;
ch.push_str(s); ch.push_str(s);
ch ch
}; };
*args[0].write_lock::<Dynamic>().expect(BUILTIN) = ch.into(); *args[0].write_lock::<Dynamic>().unwrap() = ch.into();
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
}), },
CHECKED_BUILD,
)),
_ => None, _ => None,
}; };
} }
@ -899,21 +923,23 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt
use crate::Array; use crate::Array;
return match op { return match op {
PlusAssign => Some(|_ctx, args| { PlusAssign => Some((
{ |_ctx, args| {
let x = std::mem::take(args[1]); {
let array = &mut *args[0].write_lock::<Array>().expect(BUILTIN); let x = std::mem::take(args[1]);
push(array, x); let array = &mut *args[0].write_lock::<Array>().unwrap();
} push(array, x);
}
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
_ctx.engine().check_data_size( _ctx.unwrap()
&*args[0].read_lock().expect(BUILTIN), .engine()
crate::Position::NONE, .check_data_size(&*args[0].read_lock().unwrap(), crate::Position::NONE)?;
)?;
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
}), },
CHECKED_BUILD,
)),
_ => None, _ => None,
}; };
} }
@ -928,16 +954,20 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt
use crate::packages::blob_basic::blob_functions::*; use crate::packages::blob_basic::blob_functions::*;
return match op { return match op {
PlusAssign => Some(|_ctx, args| { PlusAssign => Some((
let x = args[1].as_int().expect(BUILTIN); |_ctx, args| {
let blob = &mut *args[0].write_lock::<Blob>().expect(BUILTIN); let x = args[1].as_int().unwrap();
let blob = &mut *args[0].write_lock::<Blob>().unwrap();
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
_ctx.engine() _ctx.unwrap()
.raise_err_if_over_data_size_limit((blob.len() + 1, 0, 0))?; .engine()
.throw_on_size((blob.len() + 1, 0, 0))?;
Ok(push(blob, x).into()) Ok(push(blob, x).into())
}), },
CHECKED_BUILD,
)),
_ => None, _ => None,
}; };
} }
@ -948,16 +978,20 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt
use crate::packages::blob_basic::blob_functions::*; use crate::packages::blob_basic::blob_functions::*;
return match op { return match op {
PlusAssign => Some(|_ctx, args| { PlusAssign => Some((
let x = args[1].as_char().expect(BUILTIN); |_ctx, args| {
let blob = &mut *args[0].write_lock::<Blob>().expect(BUILTIN); let x = args[1].as_char().unwrap();
let blob = &mut *args[0].write_lock::<Blob>().unwrap();
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
_ctx.engine() _ctx.unwrap()
.raise_err_if_over_data_size_limit((blob.len() + 1, 0, 0))?; .engine()
.throw_on_size((blob.len() + 1, 0, 0))?;
Ok(append_char(blob, x).into()) Ok(append_char(blob, x).into())
}), },
CHECKED_BUILD,
)),
_ => None, _ => None,
}; };
} }
@ -968,24 +1002,25 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt
use crate::packages::blob_basic::blob_functions::*; use crate::packages::blob_basic::blob_functions::*;
return match op { return match op {
PlusAssign => Some(|_ctx, args| { PlusAssign => Some((
let (first, second) = args.split_first_mut().expect(BUILTIN); |_ctx, args| {
let blob = &mut *first.write_lock::<Blob>().expect(BUILTIN); let (first, second) = args.split_first_mut().unwrap();
let s = &*second[0].read_lock::<ImmutableString>().expect(BUILTIN); let blob = &mut *first.write_lock::<Blob>().unwrap();
let s = &*second[0].read_lock::<ImmutableString>().unwrap();
if s.is_empty() { if s.is_empty() {
return Ok(Dynamic::UNIT); return Ok(Dynamic::UNIT);
} }
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
_ctx.engine().raise_err_if_over_data_size_limit(( _ctx.unwrap()
blob.len() + s.len(), .engine()
0, .throw_on_size((blob.len() + s.len(), 0, 0))?;
0,
))?;
Ok(append_str(blob, s).into()) Ok(append_str(blob, s).into())
}), },
CHECKED_BUILD,
)),
_ => None, _ => None,
}; };
} }

View File

@ -8,8 +8,7 @@ use crate::engine::{
KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
}; };
use crate::eval::{Caches, FnResolutionCacheEntry, GlobalRuntimeState}; use crate::eval::{Caches, FnResolutionCacheEntry, GlobalRuntimeState};
use crate::tokenizer::{is_valid_function_name, Token}; use crate::tokenizer::{is_valid_function_name, Token, NO_TOKEN};
use crate::types::RestoreOnDrop;
use crate::{ use crate::{
calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnArgsVec, FnPtr, ImmutableString, calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnArgsVec, FnPtr, ImmutableString,
OptimizationLevel, Position, RhaiError, RhaiResult, RhaiResultOf, Scope, Shared, ERR, OptimizationLevel, Position, RhaiError, RhaiResult, RhaiResultOf, Scope, Shared, ERR,
@ -165,7 +164,7 @@ impl Engine {
_global: &GlobalRuntimeState, _global: &GlobalRuntimeState,
caches: &'s mut Caches, caches: &'s mut Caches,
local_entry: &'s mut Option<FnResolutionCacheEntry>, local_entry: &'s mut Option<FnResolutionCacheEntry>,
op_token: Option<&Token>, op_token: Token,
hash_base: u64, hash_base: u64,
args: Option<&mut FnCallArgs>, args: Option<&mut FnCallArgs>,
allow_dynamic: bool, allow_dynamic: bool,
@ -174,7 +173,7 @@ impl Engine {
return None; return None;
} }
let mut hash = args.as_ref().map_or(hash_base, |args| { let mut hash = args.as_deref().map_or(hash_base, |args| {
calc_fn_hash_full(hash_base, args.iter().map(|a| a.type_id())) calc_fn_hash_full(hash_base, args.iter().map(|a| a.type_id()))
}); });
@ -183,7 +182,7 @@ impl Engine {
match cache.map.entry(hash) { match cache.map.entry(hash) {
Entry::Occupied(entry) => entry.into_mut().as_ref(), Entry::Occupied(entry) => entry.into_mut().as_ref(),
Entry::Vacant(entry) => { Entry::Vacant(entry) => {
let num_args = args.as_ref().map_or(0, |a| a.len()); let num_args = args.as_deref().map_or(0, |a| a.len());
let mut max_bitmask = 0; // One above maximum bitmask based on number of parameters. let mut max_bitmask = 0; // One above maximum bitmask based on number of parameters.
// Set later when a specific matching function is not found. // Set later when a specific matching function is not found.
let mut bitmask = 1usize; // Bitmask of which parameter to replace with `Dynamic` let mut bitmask = 1usize; // Bitmask of which parameter to replace with `Dynamic`
@ -212,9 +211,12 @@ impl Engine {
} else { } else {
func.or_else(|| _global.get_qualified_fn(hash)).or_else(|| { func.or_else(|| _global.get_qualified_fn(hash)).or_else(|| {
self.global_sub_modules self.global_sub_modules
.iter() .as_deref()
.flat_map(|m| m.values()) .into_iter()
.find_map(|m| m.get_qualified_fn(hash).map(|f| (f, m.id_raw()))) .flatten()
.find_map(|(_, m)| {
m.get_qualified_fn(hash).map(|f| (f, m.id_raw()))
})
}) })
}; };
@ -251,11 +253,9 @@ impl Engine {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
let is_dynamic = is_dynamic let is_dynamic = is_dynamic
|| _global.may_contain_dynamic_fn(hash_base) || _global.may_contain_dynamic_fn(hash_base)
|| self || self.global_sub_modules.as_deref().map_or(false, |m| {
.global_sub_modules m.values().any(|m| m.may_contain_dynamic_fn(hash_base))
.iter() });
.flat_map(|m| m.values())
.any(|m| m.may_contain_dynamic_fn(hash_base));
// Set maximum bitmask when there are dynamic versions of the function // Set maximum bitmask when there are dynamic versions of the function
if is_dynamic { if is_dynamic {
@ -272,22 +272,22 @@ impl Engine {
// Try to find a built-in version // Try to find a built-in version
let builtin = let builtin =
args.and_then(|args| match op_token { args.and_then(|args| match op_token {
Some(token) if token.is_op_assignment() => { Token::NONE => None,
token if token.is_op_assignment() => {
let (first_arg, rest_args) = args.split_first().unwrap(); let (first_arg, rest_args) = args.split_first().unwrap();
get_builtin_op_assignment_fn(token, first_arg, rest_args[0]) get_builtin_op_assignment_fn(token, first_arg, rest_args[0])
.map(|f| FnResolutionCacheEntry { .map(|(f, ctx)| FnResolutionCacheEntry {
func: CallableFunction::Method(Shared::new(f)), func: CallableFunction::Method(Shared::new(f), ctx),
source: None, source: None,
}) })
} }
Some(token) => get_builtin_binary_op_fn(token, args[0], args[1]) token => get_builtin_binary_op_fn(token, args[0], args[1]).map(
.map(|f| FnResolutionCacheEntry { |(f, ctx)| FnResolutionCacheEntry {
func: CallableFunction::Method(Shared::new(f)), func: CallableFunction::Method(Shared::new(f), ctx),
source: None, source: None,
}), },
),
None => None,
}); });
return if cache.filter.is_absent_and_set(hash) { return if cache.filter.is_absent_and_set(hash) {
@ -324,9 +324,9 @@ impl Engine {
} }
} }
/// # Main Entry-Point /// # Main Entry-Point (Native by Name)
/// ///
/// Call a native Rust function registered with the [`Engine`]. /// Call a native Rust function registered with the [`Engine`] by name.
/// ///
/// # WARNING /// # WARNING
/// ///
@ -340,7 +340,7 @@ impl Engine {
global: &mut GlobalRuntimeState, global: &mut GlobalRuntimeState,
caches: &mut Caches, caches: &mut Caches,
name: &str, name: &str,
op_token: Option<&Token>, op_token: Token,
hash: u64, hash: u64,
args: &mut FnCallArgs, args: &mut FnCallArgs,
is_ref_mut: bool, is_ref_mut: bool,
@ -380,12 +380,10 @@ impl Engine {
// Clone the first argument // Clone the first argument
backup.change_first_arg_to_copy(args); backup.change_first_arg_to_copy(args);
} }
auto_restore!(args if swap => move |a| backup.restore_first_arg(a));
let args =
&mut *RestoreOnDrop::lock_if(swap, args, move |a| backup.restore_first_arg(a));
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
if self.debugger.is_some() { if self.is_debugger_registered() {
let source = source.clone().or_else(|| global.source.clone()); let source = source.clone().or_else(|| global.source.clone());
global.debugger_mut().push_call_stack_frame( global.debugger_mut().push_call_stack_frame(
@ -399,25 +397,29 @@ impl Engine {
// Run external function // Run external function
let is_method = func.is_method(); let is_method = func.is_method();
let src = source.as_ref().map(|s| s.as_str()); let src = source.as_ref().map(|s| s.as_str());
let context = (self, name, src, &*global, pos).into();
let mut _result = if func.is_plugin_fn() { let context = if func.has_context() {
let f = func.get_plugin_fn().unwrap(); Some((self, name, src, &*global, pos).into())
} else {
None
};
let mut _result = if let Some(f) = func.get_plugin_fn() {
if !f.is_pure() && !args.is_empty() && args[0].is_read_only() { if !f.is_pure() && !args.is_empty() && args[0].is_read_only() {
Err(ERR::ErrorNonPureMethodCallOnConstant(name.to_string(), pos).into()) Err(ERR::ErrorNonPureMethodCallOnConstant(name.to_string(), pos).into())
} else { } else {
f.call(context, args) f.call(context, args)
.and_then(|r| self.check_data_size(r, pos))
.map_err(|err| err.fill_position(pos))
} }
} else if let Some(f) = func.get_native_fn() {
f(context, args)
} else { } else {
func.get_native_fn().unwrap()(context, args) unreachable!();
.and_then(|r| self.check_data_size(r, pos)) }
.map_err(|err| err.fill_position(pos)) .and_then(|r| self.check_data_size(r, pos))
}; .map_err(|err| err.fill_position(pos));
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
if self.debugger.is_some() { if self.is_debugger_registered() {
use crate::eval::{DebuggerEvent, DebuggerStatus}; use crate::eval::{DebuggerEvent, DebuggerStatus};
let trigger = match global.debugger().status { let trigger = match global.debugger().status {
@ -542,9 +544,9 @@ impl Engine {
} }
} }
/// # Main Entry-Point /// # Main Entry-Point (By Name)
/// ///
/// Perform an actual function call, native Rust or scripted, taking care of special functions. /// Perform an actual function call, native Rust or scripted, by name, taking care of special functions.
/// ///
/// # WARNING /// # WARNING
/// ///
@ -559,7 +561,7 @@ impl Engine {
caches: &mut Caches, caches: &mut Caches,
_scope: Option<&mut Scope>, _scope: Option<&mut Scope>,
fn_name: &str, fn_name: &str,
op_token: Option<&Token>, op_token: Token,
hashes: FnCallHashes, hashes: FnCallHashes,
mut _args: &mut FnCallArgs, mut _args: &mut FnCallArgs,
is_ref_mut: bool, is_ref_mut: bool,
@ -578,9 +580,7 @@ impl Engine {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
ensure_no_data_race(fn_name, _args, is_ref_mut)?; ensure_no_data_race(fn_name, _args, is_ref_mut)?;
let orig_level = global.level; auto_restore! { let orig_level = global.level; global.level += 1 }
global.level += 1;
let global = &mut *RestoreOnDrop::lock(global, move |g| g.level = orig_level);
// These may be redirected from method style calls. // These may be redirected from method style calls.
if hashes.is_native_only() { if hashes.is_native_only() {
@ -638,7 +638,7 @@ impl Engine {
let local_entry = &mut None; let local_entry = &mut None;
if let Some(FnResolutionCacheEntry { func, ref source }) = self if let Some(FnResolutionCacheEntry { func, ref source }) = self
.resolve_fn(global, caches, local_entry, None, hash, None, false) .resolve_fn(global, caches, local_entry, NO_TOKEN, hash, None, false)
.cloned() .cloned()
{ {
// Script function call // Script function call
@ -659,7 +659,7 @@ impl Engine {
}; };
let orig_source = mem::replace(&mut global.source, source.clone()); let orig_source = mem::replace(&mut global.source, source.clone());
let global = &mut *RestoreOnDrop::lock(global, move |g| g.source = orig_source); auto_restore!(global => move |g| g.source = orig_source);
return if _is_method_call { return if _is_method_call {
// Method call of script function - map first argument to `this` // Method call of script function - map first argument to `this`
@ -679,9 +679,7 @@ impl Engine {
backup.change_first_arg_to_copy(_args); backup.change_first_arg_to_copy(_args);
} }
let args = &mut *RestoreOnDrop::lock_if(swap, _args, move |a| { auto_restore!(args = (_args) if swap => move |a| backup.restore_first_arg(a));
backup.restore_first_arg(a)
});
let mut this = Dynamic::NULL; let mut this = Dynamic::NULL;
@ -721,15 +719,13 @@ impl Engine {
// Do not match function exit for arguments // Do not match function exit for arguments
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
let reset = global.debugger.as_mut().and_then(|dbg| { let reset = global.debugger.as_deref_mut().and_then(|dbg| {
dbg.clear_status_if(|status| { dbg.clear_status_if(|status| {
matches!(status, crate::eval::DebuggerStatus::FunctionExit(..)) matches!(status, crate::eval::DebuggerStatus::FunctionExit(..))
}) })
}); });
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
let global = &mut *RestoreOnDrop::lock_if(reset.is_some(), global, move |g| { auto_restore!(global if reset.is_some() => move |g| g.debugger_mut().reset_status(reset));
g.debugger_mut().reset_status(reset)
});
self.eval_expr(global, caches, scope, this_ptr, arg_expr) self.eval_expr(global, caches, scope, this_ptr, arg_expr)
.map(|r| (r, arg_expr.start_position())) .map(|r| (r, arg_expr.start_position()))
@ -782,7 +778,7 @@ impl Engine {
caches, caches,
None, None,
fn_name, fn_name,
None, NO_TOKEN,
new_hash, new_hash,
&mut args, &mut args,
false, false,
@ -836,7 +832,7 @@ impl Engine {
caches, caches,
None, None,
&fn_name, &fn_name,
None, NO_TOKEN,
new_hash, new_hash,
&mut args, &mut args,
is_ref_mut, is_ref_mut,
@ -935,7 +931,7 @@ impl Engine {
caches, caches,
None, None,
fn_name, fn_name,
None, NO_TOKEN,
hash, hash,
&mut args, &mut args,
is_ref_mut, is_ref_mut,
@ -961,7 +957,7 @@ impl Engine {
scope: &mut Scope, scope: &mut Scope,
this_ptr: &mut Dynamic, this_ptr: &mut Dynamic,
fn_name: &str, fn_name: &str,
op_token: Option<&Token>, op_token: Token,
first_arg: Option<&Expr>, first_arg: Option<&Expr>,
args_expr: &[Expr], args_expr: &[Expr],
hashes: FnCallHashes, hashes: FnCallHashes,
@ -977,7 +973,7 @@ impl Engine {
let redirected; // Handle call() - Redirect function call let redirected; // Handle call() - Redirect function call
match name { match name {
_ if op_token.is_some() => (), _ if op_token != NO_TOKEN => (),
// Handle call() // Handle call()
KEYWORD_FN_PTR_CALL if total_args >= 1 => { KEYWORD_FN_PTR_CALL if total_args >= 1 => {
@ -1208,12 +1204,7 @@ impl Engine {
self.track_operation(global, first_expr.position())?; self.track_operation(global, first_expr.position())?;
#[cfg(not(feature = "no_closure"))] if target.is_shared() || target.is_temp_value() {
let target_is_shared = target.is_shared();
#[cfg(feature = "no_closure")]
let target_is_shared = false;
if target_is_shared || target.is_temp_value() {
arg_values.insert(0, target.take_or_clone().flatten()); arg_values.insert(0, target.take_or_clone().flatten());
} else { } else {
// Turn it into a method call only if the object is not shared and not a simple value // Turn it into a method call only if the object is not shared and not a simple value
@ -1370,9 +1361,7 @@ impl Engine {
} }
} }
let orig_level = global.level; auto_restore! { let orig_level = global.level; global.level += 1 }
global.level += 1;
let global = &mut *RestoreOnDrop::lock(global, move |g| g.level = orig_level);
match func { match func {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
@ -1382,7 +1371,7 @@ impl Engine {
let mut this = Dynamic::NULL; let mut this = Dynamic::NULL;
let orig_source = mem::replace(&mut global.source, module.id_raw().cloned()); let orig_source = mem::replace(&mut global.source, module.id_raw().cloned());
let global = &mut *RestoreOnDrop::lock(global, move |g| g.source = orig_source); auto_restore!(global => move |g| g.source = orig_source);
self.call_script_fn( self.call_script_fn(
global, caches, new_scope, &mut this, fn_def, &mut args, true, pos, global, caches, new_scope, &mut this, fn_def, &mut args, true, pos,
@ -1390,8 +1379,12 @@ impl Engine {
} }
Some(f) if f.is_plugin_fn() => { Some(f) if f.is_plugin_fn() => {
let context = (self, fn_name, module.id(), &*global, pos).into();
let f = f.get_plugin_fn().expect("plugin function"); 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
};
if !f.is_pure() && !args.is_empty() && args[0].is_read_only() { if !f.is_pure() && !args.is_empty() && args[0].is_read_only() {
Err(ERR::ErrorNonPureMethodCallOnConstant(fn_name.to_string(), pos).into()) Err(ERR::ErrorNonPureMethodCallOnConstant(fn_name.to_string(), pos).into())
} else { } else {
@ -1402,7 +1395,11 @@ impl Engine {
Some(f) if f.is_native() => { Some(f) if f.is_native() => {
let func = f.get_native_fn().expect("native function"); let func = f.get_native_fn().expect("native function");
let context = (self, fn_name, module.id(), &*global, pos).into(); let context = if f.has_context() {
Some((self, fn_name, module.id(), &*global, pos).into())
} else {
None
};
func(context, &mut args).and_then(|r| self.check_data_size(r, pos)) func(context, &mut args).and_then(|r| self.check_data_size(r, pos))
} }
@ -1467,6 +1464,8 @@ impl Engine {
self.eval_global_statements(global, caches, scope, statements) self.eval_global_statements(global, caches, scope, statements)
} }
/// # Main Entry-Point (`FnCallExpr`)
///
/// Evaluate a function call expression. /// Evaluate a function call expression.
pub(crate) fn eval_fn_call_expr( pub(crate) fn eval_fn_call_expr(
&self, &self,
@ -1488,10 +1487,26 @@ impl Engine {
.. ..
} = expr; } = expr;
let op_token = op_token.as_ref(); let op_token = op_token.clone();
// Short-circuit native unary operator call if under Fast Operators mode
if op_token == Token::Bang && self.fast_operators() && args.len() == 1 {
let mut value = self
.get_arg_value(global, caches, scope, this_ptr, &args[0])?
.0
.flatten();
return value.as_bool().and_then(|r| Ok((!r).into())).or_else(|_| {
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 // Short-circuit native binary operator call if under Fast Operators mode
if op_token.is_some() && self.fast_operators() && args.len() == 2 { if op_token != NO_TOKEN && self.fast_operators() && args.len() == 2 {
let mut lhs = self let mut lhs = self
.get_arg_value(global, caches, scope, this_ptr, &args[0])? .get_arg_value(global, caches, scope, this_ptr, &args[0])?
.0 .0
@ -1504,15 +1519,17 @@ impl Engine {
let operands = &mut [&mut lhs, &mut rhs]; let operands = &mut [&mut lhs, &mut rhs];
if let Some(func) = if let Some((func, ctx)) =
get_builtin_binary_op_fn(op_token.as_ref().unwrap(), operands[0], operands[1]) get_builtin_binary_op_fn(op_token.clone(), operands[0], operands[1])
{ {
// Built-in found // Built-in found
let orig_level = global.level; auto_restore! { let orig_level = global.level; global.level += 1 }
global.level += 1;
let global = &*RestoreOnDrop::lock(global, move |g| g.level = orig_level);
let context = (self, name.as_str(), None, global, pos).into(); let context = if ctx {
Some((self, name.as_str(), None, &*global, pos).into())
} else {
None
};
return func(context, operands); return func(context, operands);
} }

View File

@ -14,10 +14,10 @@ use std::prelude::v1::*;
#[non_exhaustive] #[non_exhaustive]
pub enum CallableFunction { pub enum CallableFunction {
/// A pure native Rust function with all arguments passed by value. /// A pure native Rust function with all arguments passed by value.
Pure(Shared<FnAny>), Pure(Shared<FnAny>, bool),
/// A native Rust object method with the first argument passed by reference, /// A native Rust object method with the first argument passed by reference,
/// and the rest passed by value. /// and the rest passed by value.
Method(Shared<FnAny>), Method(Shared<FnAny>, bool),
/// An iterator function. /// An iterator function.
Iterator(Shared<IteratorFn>), Iterator(Shared<IteratorFn>),
/// A plugin function, /// A plugin function,
@ -136,6 +136,18 @@ impl CallableFunction {
Self::Script(..) => false, Self::Script(..) => false,
} }
} }
/// Is there a [`NativeCallContext`][crate::NativeCallContext] parameter?
#[inline]
#[must_use]
pub fn has_context(&self) -> bool {
match self {
Self::Pure(.., ctx) | Self::Method(.., ctx) => *ctx,
Self::Plugin(f) => f.has_context(),
Self::Iterator(..) => false,
#[cfg(not(feature = "no_function"))]
Self::Script(..) => false,
}
}
/// Get the access mode. /// Get the access mode.
#[inline] #[inline]
#[must_use] #[must_use]
@ -156,7 +168,7 @@ impl CallableFunction {
#[must_use] #[must_use]
pub fn get_native_fn(&self) -> Option<&Shared<FnAny>> { pub fn get_native_fn(&self) -> Option<&Shared<FnAny>> {
match self { match self {
Self::Pure(f) | Self::Method(f) => Some(f), Self::Pure(f, ..) | Self::Method(f, ..) => Some(f),
Self::Iterator(..) | Self::Plugin(..) => None, Self::Iterator(..) | Self::Plugin(..) => None,
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
@ -204,16 +216,16 @@ impl CallableFunction {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
impl From<crate::ast::ScriptFnDef> for CallableFunction { impl From<crate::ast::ScriptFnDef> for CallableFunction {
#[inline(always)] #[inline(always)]
fn from(_func: crate::ast::ScriptFnDef) -> Self { fn from(func: crate::ast::ScriptFnDef) -> Self {
Self::Script(_func.into()) Self::Script(func.into())
} }
} }
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
impl From<Shared<crate::ast::ScriptFnDef>> for CallableFunction { impl From<Shared<crate::ast::ScriptFnDef>> for CallableFunction {
#[inline(always)] #[inline(always)]
fn from(_func: Shared<crate::ast::ScriptFnDef>) -> Self { fn from(func: Shared<crate::ast::ScriptFnDef>) -> Self {
Self::Script(_func) Self::Script(func)
} }
} }

View File

@ -12,7 +12,7 @@ use std::prelude::v1::*;
/// Trait to create a Rust closure from a script. /// Trait to create a Rust closure from a script.
/// ///
/// Not available under `no_function`. /// Not available under `no_function`.
pub trait Func<ARGS, RET> { pub trait Func<A, R> {
/// The closure's output type. /// The closure's output type.
type Output; type Output;
@ -91,14 +91,14 @@ macro_rules! def_anonymous_fn {
impl<$($par: Variant + Clone,)* RET: Variant + Clone> Func<($($par,)*), RET> for Engine impl<$($par: Variant + Clone,)* RET: Variant + Clone> Func<($($par,)*), RET> for Engine
{ {
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
type Output = Box<dyn Fn($($par),*) -> RhaiResultOf<RET> + Send + Sync>; type Output = Box<dyn Fn($($par,)*) -> RhaiResultOf<RET> + Send + Sync>;
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
type Output = Box<dyn Fn($($par),*) -> RhaiResultOf<RET>>; type Output = Box<dyn Fn($($par,)*) -> RhaiResultOf<RET>>;
#[inline] #[inline]
fn create_from_ast(self, ast: AST, entry_point: &str) -> Self::Output { fn create_from_ast(self, ast: AST, entry_point: &str) -> Self::Output {
let fn_name: SmartString = entry_point.into(); let fn_name: SmartString = entry_point.into();
Box::new(move |$($par),*| self.call_fn(&mut Scope::new(), &ast, &fn_name, ($($par,)*))) Box::new(move |$($par,)*| self.call_fn(&mut Scope::new(), &ast, &fn_name, ($($par,)*)))
} }
#[inline] #[inline]

View File

@ -4,11 +4,11 @@ use super::call::FnCallArgs;
use crate::ast::FnCallHashes; use crate::ast::FnCallHashes;
use crate::eval::{Caches, GlobalRuntimeState}; use crate::eval::{Caches, GlobalRuntimeState};
use crate::plugin::PluginFunction; use crate::plugin::PluginFunction;
use crate::tokenizer::{is_valid_function_name, Token, TokenizeState}; use crate::tokenizer::{is_valid_function_name, Token, TokenizeState, NO_TOKEN};
use crate::types::dynamic::Variant; use crate::types::dynamic::Variant;
use crate::{ use crate::{
calc_fn_hash, reify, Dynamic, Engine, EvalContext, FuncArgs, Position, RhaiResult, calc_fn_hash, Dynamic, Engine, EvalContext, FuncArgs, Position, RhaiResult, RhaiResultOf,
RhaiResultOf, StaticVec, VarDefInfo, ERR, StaticVec, VarDefInfo, ERR,
}; };
use std::any::{type_name, TypeId}; use std::any::{type_name, TypeId};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
@ -424,8 +424,7 @@ impl<'a> NativeCallContext<'a> {
let caches = &mut Caches::new(); let caches = &mut Caches::new();
let fn_name = fn_name.as_ref(); let fn_name = fn_name.as_ref();
let op_token = Token::lookup_symbol_from_syntax(fn_name); let op_token = Token::lookup_symbol_from_syntax(fn_name).unwrap_or(NO_TOKEN);
let op_token = op_token.as_ref();
let args_len = args.len(); let args_len = args.len();
global.level += 1; global.level += 1;
@ -547,13 +546,16 @@ pub fn locked_write<T>(value: &Locked<T>) -> LockGuardMut<T> {
/// General Rust function trail object. /// General Rust function trail object.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type FnAny = dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult; pub type FnAny = dyn Fn(Option<NativeCallContext>, &mut FnCallArgs) -> RhaiResult;
/// General Rust function trail object. /// General Rust function trail object.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type FnAny = dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult + Send + Sync; pub type FnAny = dyn Fn(Option<NativeCallContext>, &mut FnCallArgs) -> RhaiResult + Send + Sync;
/// Built-in function trait object. /// Built-in function trait object.
pub type FnBuiltin = fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult; pub type FnBuiltin = (
fn(Option<NativeCallContext>, &mut FnCallArgs) -> RhaiResult,
bool,
);
/// Function that gets an iterator from a type. /// Function that gets an iterator from a type.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]

View File

@ -24,12 +24,16 @@ pub use rhai_codegen::{export_fn, register_exported_fn};
/// Use the `#[export_module]` and `#[export_fn]` procedural attributes instead. /// Use the `#[export_module]` and `#[export_fn]` procedural attributes instead.
pub trait PluginFunction { pub trait PluginFunction {
/// Call the plugin function with the arguments provided. /// Call the plugin function with the arguments provided.
fn call(&self, context: NativeCallContext, args: &mut FnCallArgs) -> RhaiResult; fn call(&self, context: Option<NativeCallContext>, args: &mut FnCallArgs) -> RhaiResult;
/// Is this plugin function a method? /// Is this plugin function a method?
#[must_use] #[must_use]
fn is_method_call(&self) -> bool; fn is_method_call(&self) -> bool;
/// Does this plugin function contain a [`NativeCallContext`] parameter?
#[must_use]
fn has_context(&self) -> bool;
/// Is this plugin function pure? /// Is this plugin function pure?
/// ///
/// This defaults to `true` such that any old implementation that has constant-checking code /// This defaults to `true` such that any old implementation that has constant-checking code

View File

@ -1,15 +1,21 @@
//! Module which defines the function registration mechanism. //! Module which defines the function registration mechanism.
#![allow(non_snake_case)] #![allow(non_snake_case)]
#![allow(unused_imports)]
#![allow(unused_mut)]
#![allow(unused_variables)]
use super::call::FnCallArgs; use super::call::FnCallArgs;
use super::callable_function::CallableFunction; use super::callable_function::CallableFunction;
use super::native::{SendSync, Shared}; use super::native::{SendSync, Shared};
use crate::types::dynamic::{DynamicWriteLock, Variant}; use crate::types::dynamic::{DynamicWriteLock, Variant};
use crate::{reify, Dynamic, NativeCallContext, RhaiResultOf}; use crate::{Dynamic, Identifier, NativeCallContext, RhaiResultOf};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{any::TypeId, mem}; use std::{
any::{type_name, TypeId},
mem,
};
/// These types are used to build a unique _marker_ tuple type for each combination /// These types are used to build a unique _marker_ tuple type for each combination
/// of function parameter types in order to make each trait implementation unique. /// of function parameter types in order to make each trait implementation unique.
@ -19,13 +25,13 @@ use std::{any::TypeId, mem};
/// ///
/// # Examples /// # Examples
/// ///
/// `RegisterNativeFunction<(Mut<A>, B, Ref<C>), R, ()>` = `Fn(&mut A, B, &C) -> R` /// `RegisterNativeFunction<(Mut<A>, B, Ref<C>), 3, false, R, false>` = `Fn(&mut A, B, &C) -> R`
/// ///
/// `RegisterNativeFunction<(Mut<A>, B, Ref<C>), R, NativeCallContext>` = `Fn(NativeCallContext, &mut A, B, &C) -> R` /// `RegisterNativeFunction<(Mut<A>, B, Ref<C>), 3, true, R, false>` = `Fn(NativeCallContext, &mut A, B, &C) -> R`
/// ///
/// `RegisterNativeFunction<(Mut<A>, B, Ref<C>), R, RhaiResultOf<()>>` = `Fn(&mut A, B, &C) -> Result<R, Box<EvalAltResult>>` /// `RegisterNativeFunction<(Mut<A>, B, Ref<C>), 3, false, R, true>` = `Fn(&mut A, B, &C) -> Result<R, Box<EvalAltResult>>`
/// ///
/// `RegisterNativeFunction<(Mut<A>, B, Ref<C>), R, RhaiResultOf<NativeCallContext>>` = `Fn(NativeCallContext, &mut A, B, &C) -> Result<R, Box<EvalAltResult>>` /// `RegisterNativeFunction<(Mut<A>, B, Ref<C>), 3, true, R, true>` = `Fn(NativeCallContext, &mut A, B, &C) -> Result<R, Box<EvalAltResult>>`
/// ///
/// These types are not actually used anywhere. /// These types are not actually used anywhere.
pub struct Mut<T>(T); pub struct Mut<T>(T);
@ -64,123 +70,137 @@ pub fn by_value<T: Variant + Clone>(data: &mut Dynamic) -> T {
/// ///
/// # Type Parameters /// # Type Parameters
/// ///
/// * `ARGS` - a tuple containing parameter types, with `&mut T` represented by `Mut<T>`. /// * `A` - a tuple containing parameter types, with `&mut T` represented by `Mut<T>`.
/// * `RET` - return type of the function; if the function returns `Result`, it is the unwrapped inner value type. /// * `N` - a constant generic containing the number of parameters, must be consistent with `ARGS`.
pub trait RegisterNativeFunction<ARGS, RET, RESULT> { /// * `X` - a constant boolean generic indicating whether there is a `NativeCallContext` parameter.
/// * `R` - return type of the function; if the function returns `Result`, it is the unwrapped inner value type.
/// * `F` - a constant boolean generic indicating whether the function is fallible (i.e. returns `Result<T, Box<EvalAltResult>>`).
pub trait RegisterNativeFunction<
A: 'static,
const N: usize,
const X: bool,
R: 'static,
const F: bool,
>
{
/// Convert this function into a [`CallableFunction`]. /// Convert this function into a [`CallableFunction`].
#[must_use] #[must_use]
fn into_callable_function(self) -> CallableFunction; fn into_callable_function(self, name: Identifier, no_const: bool) -> CallableFunction;
/// Get the type ID's of this function's parameters. /// Get the type ID's of this function's parameters.
#[must_use] #[must_use]
fn param_types() -> Box<[TypeId]>; fn param_types() -> [TypeId; N];
/// Get the number of parameters for this function.
#[inline(always)]
#[must_use]
fn num_params() -> usize {
N
}
/// Is there a [`NativeCallContext`] parameter for this function?
#[inline(always)]
#[must_use]
fn has_context() -> bool {
X
}
/// _(metadata)_ Get the type names of this function's parameters. /// _(metadata)_ Get the type names of this function's parameters.
/// Exported under the `metadata` feature only. /// Exported under the `metadata` feature only.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
#[must_use] #[must_use]
fn param_names() -> Box<[&'static str]>; fn param_names() -> [&'static str; N];
/// _(metadata)_ Get the type ID of this function's return value. /// _(metadata)_ Get the type ID of this function's return value.
/// Exported under the `metadata` feature only. /// Exported under the `metadata` feature only.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
#[inline(always)]
#[must_use] #[must_use]
fn return_type() -> TypeId; fn return_type() -> TypeId {
if F {
TypeId::of::<RhaiResultOf<R>>()
} else {
TypeId::of::<R>()
}
}
/// _(metadata)_ Get the type name of this function's return value. /// _(metadata)_ Get the type name of this function's return value.
/// Exported under the `metadata` feature only. /// Exported under the `metadata` feature only.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
fn return_type_name() -> &'static str { fn return_type_name() -> &'static str {
std::any::type_name::<RET>() type_name::<R>()
} }
} }
const EXPECT_ARGS: &str = "arguments";
macro_rules! check_constant { macro_rules! check_constant {
($abi:ident, $ctx:ident, $args:ident) => { ($abi:ident, $n:expr, $fn_name:ident, $no_const:ident, $args:ident) => {
#[cfg(any(not(feature = "no_object"), not(feature = "no_index")))] #[cfg(any(not(feature = "no_object"), not(feature = "no_index")))]
if stringify!($abi) == "Method" && !$args.is_empty() { if stringify!($abi) == "Method" && $no_const && $args[0].is_read_only() {
let deny = match $args.len() { return Err(crate::ERR::ErrorNonPureMethodCallOnConstant(
#[cfg(not(feature = "no_index"))] $fn_name.to_string(),
3 if $ctx.fn_name() == crate::engine::FN_IDX_SET && $args[0].is_read_only() => true, crate::Position::NONE,
#[cfg(not(feature = "no_object"))] )
2 if $ctx.fn_name().starts_with(crate::engine::FN_SET) .into());
&& $args[0].is_read_only() =>
{
true
}
_ => false,
};
if deny {
return Err(crate::ERR::ErrorNonPureMethodCallOnConstant(
$ctx.fn_name().to_string(),
crate::Position::NONE,
)
.into());
}
} }
}; };
} }
macro_rules! def_register { macro_rules! def_register {
() => { () => {
def_register!(imp Pure :); def_register!(imp Pure : 0;);
}; };
(imp $abi:ident : $($par:ident => $arg:expr => $mark:ty => $param:ty => $let:stmt => $clone:expr),*) => { (imp $abi:ident : $n:expr ; $($par:ident => $arg:expr => $mark:ty => $param:ty => $clone:expr),*) => {
// ^ function ABI type // ^ function ABI type
// ^ function parameter generic type name (A, B, C etc.) // ^ number of parameters
// ^ call argument(like A, *B, &mut C etc) // ^ function parameter generic type name (A, B, C etc.)
// ^ function parameter marker type (A, Ref<B> or Mut<C>) // ^ call argument(like A, *B, &mut C etc)
// ^ function parameter actual type (A, &B or &mut C) // ^ function parameter marker type (A, Ref<B> or Mut<C>)
// ^ argument let statement // ^ function parameter actual type (A, &B or &mut C)
// ^ parameter access function (by_value or by_ref)
impl< impl<
FN: Fn($($param),*) -> RET + SendSync + 'static, FN: Fn($($param),*) -> RET + SendSync + 'static,
$($par: Variant + Clone,)* $($par: Variant + Clone,)*
RET: Variant + Clone RET: Variant + Clone,
> RegisterNativeFunction<($($mark,)*), RET, ()> for FN { > RegisterNativeFunction<($($mark,)*), $n, false, RET, false> for FN {
#[inline(always)] fn param_types() -> Box<[TypeId]> { vec![$(TypeId::of::<$par>()),*].into_boxed_slice() } #[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] }
#[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> Box<[&'static str]> { vec![$(std::any::type_name::<$param>()),*].into_boxed_slice() } #[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::<RET>() } #[inline(always)] fn into_callable_function(self, fn_name: Identifier, no_const: bool) -> CallableFunction {
#[inline(always)] fn into_callable_function(self) -> CallableFunction { CallableFunction::$abi(Shared::new(move |_, args: &mut FnCallArgs| {
CallableFunction::$abi(Shared::new(move |_ctx: NativeCallContext, args: &mut FnCallArgs| {
// The arguments are assumed to be of the correct number and types! // The arguments are assumed to be of the correct number and types!
check_constant!($abi, _ctx, args); check_constant!($abi, $n, fn_name, no_const, args);
let mut _drain = args.iter_mut(); let mut drain = args.iter_mut();
$($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )* $(let mut $par = $clone(drain.next().unwrap()); )*
// Call the function with each argument value // Call the function with each argument value
let r = self($($arg),*); let r = self($($arg),*);
// Map the result // Map the result
Ok(Dynamic::from(r)) Ok(Dynamic::from(r))
})) }), false)
} }
} }
impl< impl<
FN: for<'a> Fn(NativeCallContext<'a>, $($param),*) -> RET + SendSync + 'static, FN: for<'a> Fn(NativeCallContext<'a>, $($param),*) -> RET + SendSync + 'static,
$($par: Variant + Clone,)* $($par: Variant + Clone,)*
RET: Variant + Clone RET: Variant + Clone,
> RegisterNativeFunction<($($mark,)*), RET, NativeCallContext<'static>> for FN { > RegisterNativeFunction<($($mark,)*), $n, true, RET, false> for FN {
#[inline(always)] fn param_types() -> Box<[TypeId]> { vec![$(TypeId::of::<$par>()),*].into_boxed_slice() } #[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] }
#[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> Box<[&'static str]> { vec![$(std::any::type_name::<$param>()),*].into_boxed_slice() } #[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::<RET>() } #[inline(always)] fn into_callable_function(self, fn_name: Identifier, no_const: bool) -> CallableFunction {
#[inline(always)] fn into_callable_function(self) -> CallableFunction { CallableFunction::$abi(Shared::new(move |ctx: Option<NativeCallContext>, args: &mut FnCallArgs| {
CallableFunction::$abi(Shared::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| { let ctx = ctx.unwrap();
// The arguments are assumed to be of the correct number and types!
check_constant!($abi, ctx, args);
let mut _drain = args.iter_mut(); // The arguments are assumed to be of the correct number and types!
$($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )* check_constant!($abi, $n, fn_name, no_const, args);
let mut drain = args.iter_mut();
$(let mut $par = $clone(drain.next().unwrap()); )*
// Call the function with each argument value // Call the function with each argument value
let r = self(ctx, $($arg),*); let r = self(ctx, $($arg),*);
// Map the result // Map the result
Ok(Dynamic::from(r)) Ok(Dynamic::from(r))
})) }), true)
} }
} }
@ -188,22 +208,21 @@ macro_rules! def_register {
FN: Fn($($param),*) -> RhaiResultOf<RET> + SendSync + 'static, FN: Fn($($param),*) -> RhaiResultOf<RET> + SendSync + 'static,
$($par: Variant + Clone,)* $($par: Variant + Clone,)*
RET: Variant + Clone RET: Variant + Clone
> RegisterNativeFunction<($($mark,)*), RET, RhaiResultOf<()>> for FN { > RegisterNativeFunction<($($mark,)*), $n, false, RET, true> for FN {
#[inline(always)] fn param_types() -> Box<[TypeId]> { vec![$(TypeId::of::<$par>()),*].into_boxed_slice() } #[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] }
#[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> Box<[&'static str]> { vec![$(std::any::type_name::<$param>()),*].into_boxed_slice() } #[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::<RhaiResultOf<RET>>() } #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { type_name::<RhaiResultOf<RET>>() }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<RhaiResultOf<RET>>() } #[inline(always)] fn into_callable_function(self, fn_name: Identifier, no_const: bool) -> CallableFunction {
#[inline(always)] fn into_callable_function(self) -> CallableFunction { CallableFunction::$abi(Shared::new(move |_, args: &mut FnCallArgs| {
CallableFunction::$abi(Shared::new(move |_ctx: NativeCallContext, args: &mut FnCallArgs| {
// The arguments are assumed to be of the correct number and types! // The arguments are assumed to be of the correct number and types!
check_constant!($abi, _ctx, args); check_constant!($abi, $n, fn_name, no_const, args);
let mut _drain = args.iter_mut(); let mut drain = args.iter_mut();
$($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )* $(let mut $par = $clone(drain.next().unwrap()); )*
// Call the function with each argument value // Call the function with each argument value
self($($arg),*).map(Dynamic::from) self($($arg),*).map(Dynamic::from)
})) }), false)
} }
} }
@ -211,40 +230,41 @@ macro_rules! def_register {
FN: for<'a> Fn(NativeCallContext<'a>, $($param),*) -> RhaiResultOf<RET> + SendSync + 'static, FN: for<'a> Fn(NativeCallContext<'a>, $($param),*) -> RhaiResultOf<RET> + SendSync + 'static,
$($par: Variant + Clone,)* $($par: Variant + Clone,)*
RET: Variant + Clone RET: Variant + Clone
> RegisterNativeFunction<($($mark,)*), RET, RhaiResultOf<NativeCallContext<'static>>> for FN { > RegisterNativeFunction<($($mark,)*), $n, true, RET, true> for FN {
#[inline(always)] fn param_types() -> Box<[TypeId]> { vec![$(TypeId::of::<$par>()),*].into_boxed_slice() } #[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] }
#[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> Box<[&'static str]> { vec![$(std::any::type_name::<$param>()),*].into_boxed_slice() } #[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::<RhaiResultOf<RET>>() } #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { type_name::<RhaiResultOf<RET>>() }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<RhaiResultOf<RET>>() } #[inline(always)] fn into_callable_function(self, fn_name: Identifier, no_const: bool) -> CallableFunction {
#[inline(always)] fn into_callable_function(self) -> CallableFunction { CallableFunction::$abi(Shared::new(move |ctx: Option<NativeCallContext>, args: &mut FnCallArgs| {
CallableFunction::$abi(Shared::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| { let ctx = ctx.unwrap();
// The arguments are assumed to be of the correct number and types!
check_constant!($abi, ctx, args);
let mut _drain = args.iter_mut(); // The arguments are assumed to be of the correct number and types!
$($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )* check_constant!($abi, $n, fn_name, no_const, args);
let mut drain = args.iter_mut();
$(let mut $par = $clone(drain.next().unwrap()); )*
// Call the function with each argument value // Call the function with each argument value
self(ctx, $($arg),*).map(Dynamic::from) self(ctx, $($arg),*).map(Dynamic::from)
})) }), true)
} }
} }
//def_register!(imp_pop $($par => $mark => $param),*); //def_register!(imp_pop $($par => $mark => $param),*);
}; };
($p0:ident $(, $p:ident)*) => { ($p0:ident:$n0:expr $(, $p:ident: $n:expr)*) => {
def_register!(imp Pure : $p0 => $p0 => $p0 => $p0 => let $p0 => by_value $(, $p => $p => $p => $p => let $p => by_value)*); def_register!(imp Pure : $n0 ; $p0 => $p0 => $p0 => $p0 => by_value $(, $p => $p => $p => $p => by_value)*);
def_register!(imp Method : $p0 => &mut $p0 => Mut<$p0> => &mut $p0 => let mut $p0 => by_ref $(, $p => $p => $p => $p => let $p => by_value)*); def_register!(imp Method : $n0 ; $p0 => &mut $p0 => Mut<$p0> => &mut $p0 => by_ref $(, $p => $p => $p => $p => by_value)*);
// ^ CallableFunction constructor // ^ CallableFunction constructor
// ^ first parameter passed through // ^ number of arguments ^ first parameter passed through
// ^ others passed by value (by_value) // ^ others passed by value (by_value)
// Currently does not support first argument which is a reference, as there will be // Currently does not support first argument which is a reference, as there will be
// conflicting implementations since &T: Any and T: Any cannot be distinguished // conflicting implementations since &T: Any and T: Any cannot be distinguished
//def_register!(imp $p0 => Ref<$p0> => &$p0 => by_ref $(, $p => $p => $p => by_value)*); //def_register!(imp $p0 => Ref<$p0> => &$p0 => by_ref $(, $p => $p => $p => by_value)*);
def_register!($($p),*); def_register!($($p: $n),*);
}; };
} }
def_register!(A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V); def_register!(A:20, B:19, C:18, D:17, E:16, F:15, G:14, H:13, J:12, K:11, L:10, M:9, N:8, P:7, Q:6, R:5, S:4, T:3, U:2, V:1);

View File

@ -4,7 +4,7 @@
use super::call::FnCallArgs; use super::call::FnCallArgs;
use crate::ast::ScriptFnDef; use crate::ast::ScriptFnDef;
use crate::eval::{Caches, GlobalRuntimeState}; use crate::eval::{Caches, GlobalRuntimeState};
use crate::{Dynamic, Engine, Position, RhaiError, RhaiResult, Scope, ERR}; use crate::{Dynamic, Engine, Position, RhaiResult, Scope, ERR};
use std::mem; use std::mem;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -33,32 +33,6 @@ impl Engine {
rewind_scope: bool, rewind_scope: bool,
pos: Position, pos: Position,
) -> RhaiResult { ) -> RhaiResult {
#[cold]
#[inline(never)]
fn make_error(
name: String,
_fn_def: &ScriptFnDef,
global: &GlobalRuntimeState,
err: RhaiError,
pos: Position,
) -> RhaiResult {
#[cfg(not(feature = "no_module"))]
let source = _fn_def
.environ
.as_ref()
.and_then(|environ| environ.lib.id().map(str::to_string));
#[cfg(feature = "no_module")]
let source = None;
Err(ERR::ErrorInFunctionCall(
name,
source.unwrap_or_else(|| global.source().unwrap_or("").to_string()),
err,
pos,
)
.into())
}
assert!(fn_def.params.len() == args.len()); assert!(fn_def.params.len() == args.len());
self.track_operation(global, pos)?; self.track_operation(global, pos)?;
@ -69,7 +43,7 @@ impl Engine {
} }
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
if self.debugger.is_none() && fn_def.body.is_empty() { if self.debugger_interface.is_none() && fn_def.body.is_empty() {
return Ok(Dynamic::UNIT); return Ok(Dynamic::UNIT);
} }
#[cfg(not(feature = "debugging"))] #[cfg(not(feature = "debugging"))]
@ -96,15 +70,14 @@ impl Engine {
// Push a new call stack frame // Push a new call stack frame
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
if self.debugger.is_some() { if self.is_debugger_registered() {
let fn_name = fn_def.name.clone();
let args = scope.iter().skip(orig_scope_len).map(|(.., v)| v).collect();
let source = global.source.clone(); let source = global.source.clone();
global.debugger_mut().push_call_stack_frame( global
fn_def.name.clone(), .debugger_mut()
scope.iter().skip(orig_scope_len).map(|(.., v)| v).collect(), .push_call_stack_frame(fn_name, args, source, pos);
source,
pos,
);
} }
// Merge in encapsulated environment, if any // Merge in encapsulated environment, if any
@ -131,37 +104,42 @@ impl Engine {
}; };
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
if self.debugger.is_some() { if self.is_debugger_registered() {
let node = crate::ast::Stmt::Noop(fn_def.body.position()); let node = crate::ast::Stmt::Noop(fn_def.body.position());
self.run_debugger(global, caches, scope, this_ptr, &node)?; self.run_debugger(global, caches, scope, this_ptr, &node)?;
} }
// Evaluate the function // Evaluate the function
let mut _result = self let mut _result: RhaiResult = self
.eval_stmt_block(global, caches, scope, this_ptr, &fn_def.body, rewind_scope) .eval_stmt_block(global, caches, scope, this_ptr, &fn_def.body, rewind_scope)
.or_else(|err| match *err { .or_else(|err| match *err {
// Convert return statement to return value // Convert return statement to return value
ERR::Return(x, ..) => Ok(x), ERR::Return(x, ..) => Ok(x),
// Error in sub function call
ERR::ErrorInFunctionCall(name, src, err, ..) => {
let fn_name = if src.is_empty() {
format!("{name} < {}", fn_def.name)
} else {
format!("{name} @ '{src}' < {}", fn_def.name)
};
make_error(fn_name, fn_def, global, err, pos)
}
// System errors are passed straight-through // System errors are passed straight-through
mut err if err.is_system_exception() => { mut err if err.is_system_exception() => {
err.set_position(pos); err.set_position(pos);
Err(err.into()) Err(err.into())
} }
// Other errors are wrapped in `ErrorInFunctionCall` // Other errors are wrapped in `ErrorInFunctionCall`
_ => make_error(fn_def.name.to_string(), fn_def, global, err, pos), _ => Err(ERR::ErrorInFunctionCall(
fn_def.name.to_string(),
#[cfg(not(feature = "no_module"))]
fn_def
.environ
.as_deref()
.and_then(|environ| environ.lib.id())
.unwrap_or_else(|| global.source().unwrap_or(""))
.to_string(),
#[cfg(feature = "no_module")]
global.source().unwrap_or("").to_string(),
err,
pos,
)
.into()),
}); });
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
if self.debugger.is_some() { if self.is_debugger_registered() {
let trigger = match global.debugger_mut().status { let trigger = match global.debugger_mut().status {
crate::eval::DebuggerStatus::FunctionExit(n) => n >= global.level, crate::eval::DebuggerStatus::FunctionExit(n) => n >= global.level,
crate::eval::DebuggerStatus::Next(.., true) => true, crate::eval::DebuggerStatus::Next(.., true) => true,
@ -236,7 +214,9 @@ impl Engine {
// Then check imported modules // Then check imported modules
global.contains_qualified_fn(hash_script) global.contains_qualified_fn(hash_script)
// Then check sub-modules // Then check sub-modules
|| self.global_sub_modules.iter().flat_map(|m| m.values()).any(|m| m.contains_qualified_fn(hash_script)); || self.global_sub_modules.as_deref().map_or(false, |m| {
m.values().any(|m| m.contains_qualified_fn(hash_script))
});
if !result && !cache.filter.is_absent_and_set(hash_script) { if !result && !cache.filter.is_absent_and_set(hash_script) {
// Do not cache "one-hit wonders" // Do not cache "one-hit wonders"

View File

@ -85,6 +85,11 @@ extern crate no_std_compat as std;
use std::prelude::v1::*; use std::prelude::v1::*;
// Internal modules // Internal modules
#[macro_use]
mod reify;
#[macro_use]
mod types;
mod api; mod api;
mod ast; mod ast;
pub mod config; pub mod config;
@ -95,10 +100,8 @@ mod module;
mod optimizer; mod optimizer;
pub mod packages; pub mod packages;
mod parser; mod parser;
mod reify;
mod tests; mod tests;
mod tokenizer; mod tokenizer;
mod types;
/// Error encountered when parsing a script. /// Error encountered when parsing a script.
type PERR = ParseErrorType; type PERR = ParseErrorType;
@ -207,9 +210,9 @@ pub use engine::{Engine, OP_CONTAINS, OP_EQUALS};
pub use eval::EvalContext; pub use eval::EvalContext;
pub use func::{NativeCallContext, RegisterNativeFunction}; pub use func::{NativeCallContext, RegisterNativeFunction};
pub use module::{FnNamespace, Module}; pub use module::{FnNamespace, Module};
pub use tokenizer::Position;
#[cfg(not(feature = "no_time"))] #[cfg(not(feature = "no_time"))]
pub use types::Instant; pub use types::Instant;
pub use types::Position;
pub use types::{ pub use types::{
Dynamic, EvalAltResult, FnPtr, ImmutableString, LexError, ParseError, ParseErrorType, Scope, Dynamic, EvalAltResult, FnPtr, ImmutableString, LexError, ParseError, ParseErrorType, Scope,
}; };
@ -311,12 +314,12 @@ pub use types::dynamic::{AccessMode, DynamicReadLock, DynamicWriteLock, Variant}
pub use types::FloatWrapper; pub use types::FloatWrapper;
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
pub use types::StringsInterner; pub use types::{Span, StringsInterner};
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
pub use tokenizer::{ pub use tokenizer::{
get_next_token, is_valid_function_name, is_valid_identifier, parse_string_literal, InputStream, get_next_token, is_valid_function_name, is_valid_identifier, parse_string_literal, InputStream,
MultiInputsStream, Span, Token, TokenIterator, TokenizeState, TokenizerControl, MultiInputsStream, Token, TokenIterator, TokenizeState, TokenizerControl,
TokenizerControlBlock, TokenizerControlBlock,
}; };

View File

@ -9,8 +9,8 @@ use crate::func::{
}; };
use crate::types::{dynamic::Variant, BloomFilterU64, CustomTypesCollection}; use crate::types::{dynamic::Variant, BloomFilterU64, CustomTypesCollection};
use crate::{ use crate::{
calc_fn_hash, calc_fn_hash_full, Dynamic, Identifier, ImmutableString, NativeCallContext, calc_fn_hash, calc_fn_hash_full, Dynamic, FnArgsVec, Identifier, ImmutableString,
RhaiResultOf, Shared, SharedModule, SmartString, StaticVec, NativeCallContext, RhaiResultOf, Shared, SharedModule, SmartString,
}; };
use bitflags::bitflags; use bitflags::bitflags;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
@ -72,20 +72,20 @@ pub struct FuncInfoMetadata {
/// Function access mode. /// Function access mode.
pub access: FnAccess, pub access: FnAccess,
/// Function name. /// Function name.
pub name: ImmutableString, pub name: Identifier,
/// Number of parameters. /// Number of parameters.
pub num_params: usize, pub num_params: usize,
/// Parameter types (if applicable). /// Parameter types (if applicable).
pub param_types: StaticVec<TypeId>, pub param_types: FnArgsVec<TypeId>,
/// Parameter names and types (if available). /// Parameter names and types (if available).
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
pub params_info: StaticVec<Identifier>, pub params_info: FnArgsVec<Identifier>,
/// Return type name. /// Return type name.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
pub return_type: Identifier, pub return_type: Identifier,
/// Comments. /// Comments.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
pub comments: Box<[Box<str>]>, pub comments: Box<[Identifier]>,
} }
/// A type containing a single registered function. /// A type containing a single registered function.
@ -115,7 +115,7 @@ impl FuncInfo {
} }
} }
} else { } else {
let params: StaticVec<_> = self let params: FnArgsVec<_> = self
.metadata .metadata
.params_info .params_info
.iter() .iter()
@ -231,7 +231,8 @@ impl fmt::Debug for Module {
"modules", "modules",
&self &self
.modules .modules
.iter() .as_deref()
.into_iter()
.flat_map(|m| m.keys()) .flat_map(|m| m.keys())
.map(SmartString::as_str) .map(SmartString::as_str)
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
@ -392,7 +393,7 @@ impl Module {
#[inline] #[inline]
#[must_use] #[must_use]
pub fn doc(&self) -> &str { pub fn doc(&self) -> &str {
self.doc.as_ref().map_or("", |s| s.as_str()) self.doc.as_deref().map_or("", SmartString::as_str)
} }
/// Set the documentation of the [`Module`]. /// Set the documentation of the [`Module`].
@ -542,16 +543,28 @@ impl Module {
#[must_use] #[must_use]
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
!self.flags.contains(ModuleFlags::INDEXED_GLOBAL_FUNCTIONS) !self.flags.contains(ModuleFlags::INDEXED_GLOBAL_FUNCTIONS)
&& self.functions.as_ref().map_or(true, |m| m.is_empty()) && self
&& self.variables.as_ref().map_or(true, |m| m.is_empty()) .functions
&& self.modules.as_ref().map_or(true, |m| m.is_empty()) .as_ref()
&& self.type_iterators.as_ref().map_or(true, |t| t.is_empty()) .map_or(true, StraightHashMap::is_empty)
&& self.all_functions.as_ref().map_or(true, |m| m.is_empty()) && self.variables.as_deref().map_or(true, BTreeMap::is_empty)
&& self.all_variables.as_ref().map_or(true, |m| m.is_empty()) && self.modules.as_deref().map_or(true, BTreeMap::is_empty)
&& self
.type_iterators
.as_deref()
.map_or(true, BTreeMap::is_empty)
&& self
.all_functions
.as_deref()
.map_or(true, StraightHashMap::is_empty)
&& self
.all_variables
.as_deref()
.map_or(true, StraightHashMap::is_empty)
&& self && self
.all_type_iterators .all_type_iterators
.as_ref() .as_deref()
.map_or(true, |m| m.is_empty()) .map_or(true, BTreeMap::is_empty)
} }
/// Is the [`Module`] indexed? /// Is the [`Module`] indexed?
@ -710,7 +723,7 @@ impl Module {
namespace: FnNamespace::Internal, namespace: FnNamespace::Internal,
access: fn_def.access, access: fn_def.access,
num_params, num_params,
param_types: StaticVec::new_const(), param_types: FnArgsVec::new_const(),
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
params_info, params_info,
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
@ -866,16 +879,12 @@ impl Module {
/// In other words, the number of entries should be one larger than the number of parameters. /// In other words, the number of entries should be one larger than the number of parameters.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
#[inline] #[inline]
pub fn update_fn_metadata<S: AsRef<str>>( pub fn update_fn_metadata<S: Into<Identifier>>(
&mut self, &mut self,
hash_fn: u64, hash_fn: u64,
arg_names: impl AsRef<[S]>, arg_names: impl IntoIterator<Item = S>,
) -> &mut Self { ) -> &mut Self {
let mut param_names: StaticVec<_> = arg_names let mut param_names: FnArgsVec<_> = arg_names.into_iter().map(Into::into).collect();
.as_ref()
.iter()
.map(|s| s.as_ref().into())
.collect();
if let Some(f) = self.functions.as_mut().and_then(|m| m.get_mut(&hash_fn)) { if let Some(f) = self.functions.as_mut().and_then(|m| m.get_mut(&hash_fn)) {
let (param_names, return_type_name) = if param_names.len() > f.metadata.num_params { let (param_names, return_type_name) = if param_names.len() > f.metadata.num_params {
@ -907,32 +916,30 @@ impl Module {
/// ///
/// ## Comments /// ## Comments
/// ///
/// Block doc-comments should be kept in a single line. /// Block doc-comments should be kept in a separate string slice.
/// ///
/// Line doc-comments should be kept in one string slice per line without the termination line-break. /// Line doc-comments should be merged, with line-breaks, into a single string slice without a final termination line-break.
/// ///
/// Leading white-spaces should be stripped, and each string slice always starts with the corresponding /// Leading white-spaces should be stripped, and each string slice always starts with the corresponding
/// doc-comment leader: `///` or `/**`. /// doc-comment leader: `///` or `/**`.
///
/// Each line in non-block doc-comments should start with `///`.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
#[inline] #[inline]
pub fn update_fn_metadata_with_comments<A: AsRef<str>, C: AsRef<str>>( pub fn update_fn_metadata_with_comments<A: Into<Identifier>, C: Into<Identifier>>(
&mut self, &mut self,
hash_fn: u64, hash_fn: u64,
arg_names: impl AsRef<[A]>, arg_names: impl IntoIterator<Item = A>,
comments: impl AsRef<[C]>, comments: impl IntoIterator<Item = C>,
) -> &mut Self { ) -> &mut Self {
self.update_fn_metadata(hash_fn, arg_names); self.update_fn_metadata(hash_fn, arg_names);
let comments = comments.as_ref(); self.functions
.as_mut()
if !comments.is_empty() { .and_then(|m| m.get_mut(&hash_fn))
let f = self .unwrap()
.functions .metadata
.as_mut() .comments = comments.into_iter().map(Into::into).collect();
.and_then(|m| m.get_mut(&hash_fn))
.unwrap();
f.metadata.comments = comments.iter().map(|s| s.as_ref().into()).collect();
}
self self
} }
@ -997,12 +1004,11 @@ impl Module {
let _arg_names = arg_names; let _arg_names = arg_names;
let is_method = func.is_method(); let is_method = func.is_method();
let mut param_types: StaticVec<_> = arg_types let mut param_types: FnArgsVec<_> = arg_types
.as_ref() .as_ref()
.iter() .iter()
.copied()
.enumerate() .enumerate()
.map(|(i, type_id)| Self::map_type(!is_method || i > 0, type_id)) .map(|(i, &type_id)| Self::map_type(!is_method || i > 0, type_id))
.collect(); .collect();
param_types.shrink_to_fit(); param_types.shrink_to_fit();
@ -1013,11 +1019,11 @@ impl Module {
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
let (param_names, return_type_name) = { let (param_names, return_type_name) = {
let mut names = _arg_names let mut names = _arg_names
.iter() .into_iter()
.flat_map(|&p| p.iter()) .flatten()
.map(|&s| s.into()) .map(|&s| s.into())
.collect::<StaticVec<_>>(); .collect::<FnArgsVec<_>>();
let return_type = if names.len() > arg_types.as_ref().len() { let return_type = if names.len() > param_types.len() {
names.pop().unwrap() names.pop().unwrap()
} else { } else {
crate::SmartString::new_const() crate::SmartString::new_const()
@ -1086,12 +1092,14 @@ impl Module {
/// ///
/// ## Comments /// ## Comments
/// ///
/// Block doc-comments should be kept in a single line. /// Block doc-comments should be kept in a separate string slice.
/// ///
/// Line doc-comments should be kept in one string slice per line without the termination line-break. /// Line doc-comments should be merged, with line-breaks, into a single string slice without a final termination line-break.
/// ///
/// Leading white-spaces should be stripped, and each string slice always starts with the corresponding /// Leading white-spaces should be stripped, and each string slice always starts with the corresponding
/// doc-comment leader: `///` or `/**`. /// doc-comment leader: `///` or `/**`.
///
/// Each line in non-block doc-comments should start with `///`.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
#[inline] #[inline]
pub fn set_fn_with_comments<S: AsRef<str>>( pub fn set_fn_with_comments<S: AsRef<str>>(
@ -1101,17 +1109,18 @@ impl Module {
access: FnAccess, access: FnAccess,
arg_names: Option<&[&str]>, arg_names: Option<&[&str]>,
arg_types: impl AsRef<[TypeId]>, arg_types: impl AsRef<[TypeId]>,
comments: impl AsRef<[S]>, comments: impl IntoIterator<Item = S>,
func: CallableFunction, func: CallableFunction,
) -> u64 { ) -> u64 {
let hash = self.set_fn(name, namespace, access, arg_names, arg_types, func); let hash = self.set_fn(name, namespace, access, arg_names, arg_types, func);
let comments = comments.as_ref(); self.functions
.as_mut()
if !comments.is_empty() { .unwrap()
let f = self.functions.as_mut().unwrap().get_mut(&hash).unwrap(); .get_mut(&hash)
f.metadata.comments = comments.iter().map(|s| s.as_ref().into()).collect(); .unwrap()
} .metadata
.comments = comments.into_iter().map(|s| s.as_ref().into()).collect();
hash hash
} }
@ -1191,8 +1200,9 @@ impl Module {
arg_types: impl AsRef<[TypeId]>, arg_types: impl AsRef<[TypeId]>,
func: impl Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResultOf<T> + SendSync + 'static, func: impl Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResultOf<T> + SendSync + 'static,
) -> u64 { ) -> u64 {
let f = let f = move |ctx: Option<NativeCallContext>, args: &mut FnCallArgs| {
move |ctx: NativeCallContext, args: &mut FnCallArgs| func(ctx, args).map(Dynamic::from); func(ctx.unwrap(), args).map(Dynamic::from)
};
self.set_fn( self.set_fn(
name, name,
@ -1200,7 +1210,7 @@ impl Module {
access, access,
None, None,
arg_types, arg_types,
CallableFunction::Method(Shared::new(f)), CallableFunction::Method(Shared::new(f), true),
) )
} }
@ -1227,22 +1237,33 @@ impl Module {
/// assert!(module.contains_fn(hash)); /// assert!(module.contains_fn(hash));
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn set_native_fn<A, T, F, S>( pub fn set_native_fn<A: 'static, const N: usize, const C: bool, T, F>(
&mut self, &mut self,
name: impl AsRef<str> + Into<Identifier>, name: impl AsRef<str> + Into<Identifier>,
func: F, func: F,
) -> u64 ) -> u64
where where
T: Variant + Clone, T: Variant + Clone,
F: RegisterNativeFunction<A, T, RhaiResultOf<S>>, F: RegisterNativeFunction<A, N, C, T, true>,
{ {
let fn_name = name.into();
let no_const = false;
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
let no_const = no_const || (F::num_params() == 3 && fn_name == crate::engine::FN_IDX_SET);
#[cfg(not(feature = "no_object"))]
let no_const =
no_const || (F::num_params() == 2 && fn_name.starts_with(crate::engine::FN_SET));
let func = func.into_callable_function(fn_name.clone(), no_const);
self.set_fn( self.set_fn(
name, fn_name,
FnNamespace::Internal, FnNamespace::Internal,
FnAccess::Public, FnAccess::Public,
None, None,
&F::param_types(), F::param_types(),
func.into_callable_function(), func,
) )
} }
@ -1266,19 +1287,22 @@ impl Module {
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[inline(always)] #[inline(always)]
pub fn set_getter_fn<A, T, F, S>(&mut self, name: impl AsRef<str>, func: F) -> u64 pub fn set_getter_fn<A, const C: bool, T, F>(&mut self, name: impl AsRef<str>, func: F) -> u64
where where
A: Variant + Clone, A: Variant + Clone,
T: Variant + Clone, T: Variant + Clone,
F: RegisterNativeFunction<(Mut<A>,), T, RhaiResultOf<S>> + SendSync + 'static, F: RegisterNativeFunction<(Mut<A>,), 1, C, T, true> + SendSync + 'static,
{ {
let fn_name = crate::engine::make_getter(name.as_ref());
let func = func.into_callable_function(fn_name.clone(), false);
self.set_fn( self.set_fn(
crate::engine::make_getter(name.as_ref()).as_str(), fn_name,
FnNamespace::Global, FnNamespace::Global,
FnAccess::Public, FnAccess::Public,
None, None,
&F::param_types(), F::param_types(),
func.into_callable_function(), func,
) )
} }
@ -1307,19 +1331,22 @@ impl Module {
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[inline(always)] #[inline(always)]
pub fn set_setter_fn<A, T, F, S>(&mut self, name: impl AsRef<str>, func: F) -> u64 pub fn set_setter_fn<A, const C: bool, T, F>(&mut self, name: impl AsRef<str>, func: F) -> u64
where where
A: Variant + Clone, A: Variant + Clone,
T: Variant + Clone, T: Variant + Clone,
F: RegisterNativeFunction<(Mut<A>, T), (), RhaiResultOf<S>> + SendSync + 'static, F: RegisterNativeFunction<(Mut<A>, T), 2, C, (), true> + SendSync + 'static,
{ {
let fn_name = crate::engine::make_setter(name.as_ref());
let func = func.into_callable_function(fn_name.clone(), true);
self.set_fn( self.set_fn(
crate::engine::make_setter(name.as_ref()).as_str(), fn_name,
FnNamespace::Global, FnNamespace::Global,
FnAccess::Public, FnAccess::Public,
None, None,
&F::param_types(), F::param_types(),
func.into_callable_function(), func,
) )
} }
@ -1353,11 +1380,16 @@ impl Module {
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[inline(always)] #[inline(always)]
pub fn set_getter_setter_fn<A: Variant + Clone, T: Variant + Clone, S1, S2>( pub fn set_getter_setter_fn<
A: Variant + Clone,
const C1: bool,
const C2: bool,
T: Variant + Clone,
>(
&mut self, &mut self,
name: impl AsRef<str>, name: impl AsRef<str>,
getter: impl RegisterNativeFunction<(Mut<A>,), T, RhaiResultOf<S1>> + SendSync + 'static, getter: impl RegisterNativeFunction<(Mut<A>,), 1, C1, T, true> + SendSync + 'static,
setter: impl RegisterNativeFunction<(Mut<A>, T), (), RhaiResultOf<S2>> + SendSync + 'static, setter: impl RegisterNativeFunction<(Mut<A>, T), 2, C2, (), true> + SendSync + 'static,
) -> (u64, u64) { ) -> (u64, u64) {
( (
self.set_getter_fn(name.as_ref(), getter), self.set_getter_fn(name.as_ref(), getter),
@ -1394,12 +1426,12 @@ impl Module {
/// ``` /// ```
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[inline] #[inline]
pub fn set_indexer_get_fn<A, B, T, F, S>(&mut self, func: F) -> u64 pub fn set_indexer_get_fn<A, B, const C: bool, T, F>(&mut self, func: F) -> u64
where where
A: Variant + Clone, A: Variant + Clone,
B: Variant + Clone, B: Variant + Clone,
T: Variant + Clone, T: Variant + Clone,
F: RegisterNativeFunction<(Mut<A>, B), T, RhaiResultOf<S>> + SendSync + 'static, F: RegisterNativeFunction<(Mut<A>, B), 2, C, T, true> + SendSync + 'static,
{ {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
if TypeId::of::<A>() == TypeId::of::<crate::Array>() { if TypeId::of::<A>() == TypeId::of::<crate::Array>() {
@ -1421,8 +1453,8 @@ impl Module {
FnNamespace::Global, FnNamespace::Global,
FnAccess::Public, FnAccess::Public,
None, None,
&F::param_types(), F::param_types(),
func.into_callable_function(), func.into_callable_function(crate::engine::FN_IDX_GET.into(), false),
) )
} }
@ -1455,12 +1487,12 @@ impl Module {
/// ``` /// ```
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[inline] #[inline]
pub fn set_indexer_set_fn<A, B, T, F, S>(&mut self, func: F) -> u64 pub fn set_indexer_set_fn<A, B, const C: bool, T, F>(&mut self, func: F) -> u64
where where
A: Variant + Clone, A: Variant + Clone,
B: Variant + Clone, B: Variant + Clone,
T: Variant + Clone, T: Variant + Clone,
F: RegisterNativeFunction<(Mut<A>, B, T), (), RhaiResultOf<S>> + SendSync + 'static, F: RegisterNativeFunction<(Mut<A>, B, T), 3, C, (), true> + SendSync + 'static,
{ {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
if TypeId::of::<A>() == TypeId::of::<crate::Array>() { if TypeId::of::<A>() == TypeId::of::<crate::Array>() {
@ -1482,8 +1514,8 @@ impl Module {
FnNamespace::Global, FnNamespace::Global,
FnAccess::Public, FnAccess::Public,
None, None,
&F::param_types(), F::param_types(),
func.into_callable_function(), func.into_callable_function(crate::engine::FN_IDX_SET.into(), true),
) )
} }
@ -1527,13 +1559,13 @@ impl Module {
pub fn set_indexer_get_set_fn< pub fn set_indexer_get_set_fn<
A: Variant + Clone, A: Variant + Clone,
B: Variant + Clone, B: Variant + Clone,
const C1: bool,
const C2: bool,
T: Variant + Clone, T: Variant + Clone,
S1,
S2,
>( >(
&mut self, &mut self,
get_fn: impl RegisterNativeFunction<(Mut<A>, B), T, RhaiResultOf<S1>> + SendSync + 'static, get_fn: impl RegisterNativeFunction<(Mut<A>, B), 2, C1, T, true> + SendSync + 'static,
set_fn: impl RegisterNativeFunction<(Mut<A>, B, T), (), RhaiResultOf<S2>> + SendSync + 'static, set_fn: impl RegisterNativeFunction<(Mut<A>, B, T), 3, C2, (), true> + SendSync + 'static,
) -> (u64, u64) { ) -> (u64, u64) {
( (
self.set_indexer_get_fn(get_fn), self.set_indexer_get_fn(get_fn),
@ -1633,8 +1665,8 @@ impl Module {
self.flags &= !ModuleFlags::INDEXED & !ModuleFlags::INDEXED_GLOBAL_FUNCTIONS; self.flags &= !ModuleFlags::INDEXED & !ModuleFlags::INDEXED_GLOBAL_FUNCTIONS;
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
if !other.doc.as_ref().map_or(true, |s| s.is_empty()) { if !other.doc.as_deref().map_or(true, SmartString::is_empty) {
if !self.doc.as_ref().map_or(true, |s| s.is_empty()) { if !self.doc.as_deref().map_or(true, SmartString::is_empty) {
self.doc.get_or_insert_with(Default::default).push('\n'); self.doc.get_or_insert_with(Default::default).push('\n');
} }
self.doc self.doc
@ -1689,8 +1721,8 @@ impl Module {
self.flags &= !ModuleFlags::INDEXED & !ModuleFlags::INDEXED_GLOBAL_FUNCTIONS; self.flags &= !ModuleFlags::INDEXED & !ModuleFlags::INDEXED_GLOBAL_FUNCTIONS;
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
if !other.doc.as_ref().map_or(true, |s| s.is_empty()) { if !other.doc.as_deref().map_or(true, SmartString::is_empty) {
if !self.doc.as_ref().map_or(true, |s| s.is_empty()) { if !self.doc.as_deref().map_or(true, SmartString::is_empty) {
self.doc.get_or_insert_with(Default::default).push('\n'); self.doc.get_or_insert_with(Default::default).push('\n');
} }
self.doc self.doc
@ -1754,8 +1786,8 @@ impl Module {
self.flags &= !ModuleFlags::INDEXED & !ModuleFlags::INDEXED_GLOBAL_FUNCTIONS; self.flags &= !ModuleFlags::INDEXED & !ModuleFlags::INDEXED_GLOBAL_FUNCTIONS;
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
if !other.doc.as_ref().map_or(true, |s| s.is_empty()) { if !other.doc.as_deref().map_or(true, SmartString::is_empty) {
if !self.doc.as_ref().map_or(true, |s| s.is_empty()) { if !self.doc.as_deref().map_or(true, SmartString::is_empty) {
self.doc.get_or_insert_with(Default::default).push('\n'); self.doc.get_or_insert_with(Default::default).push('\n');
} }
self.doc self.doc
@ -1837,8 +1869,8 @@ impl Module {
self.flags &= !ModuleFlags::INDEXED & !ModuleFlags::INDEXED_GLOBAL_FUNCTIONS; self.flags &= !ModuleFlags::INDEXED & !ModuleFlags::INDEXED_GLOBAL_FUNCTIONS;
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
if !other.doc.as_ref().map_or(true, |s| s.is_empty()) { if !other.doc.as_deref().map_or(true, SmartString::is_empty) {
if !self.doc.as_ref().map_or(true, |s| s.is_empty()) { if !self.doc.as_deref().map_or(true, SmartString::is_empty) {
self.doc.get_or_insert_with(Default::default).push('\n'); self.doc.get_or_insert_with(Default::default).push('\n');
} }
self.doc self.doc
@ -1886,9 +1918,9 @@ impl Module {
#[must_use] #[must_use]
pub fn count(&self) -> (usize, usize, usize) { pub fn count(&self) -> (usize, usize, usize) {
( (
self.variables.as_ref().map_or(0, |m| m.len()), self.variables.as_deref().map_or(0, BTreeMap::len),
self.functions.as_ref().map_or(0, |m| m.len()), self.functions.as_ref().map_or(0, StraightHashMap::len),
self.type_iterators.as_ref().map_or(0, |t| t.len()), self.type_iterators.as_deref().map_or(0, BTreeMap::len),
) )
} }
@ -1896,16 +1928,20 @@ impl Module {
#[inline] #[inline]
pub fn iter_sub_modules(&self) -> impl Iterator<Item = (&str, &SharedModule)> { pub fn iter_sub_modules(&self) -> impl Iterator<Item = (&str, &SharedModule)> {
self.modules self.modules
.iter() .as_deref()
.flat_map(|m| m.iter().map(|(k, m)| (k.as_str(), m))) .into_iter()
.flatten()
.map(|(k, m)| (k.as_str(), m))
} }
/// Get an iterator to the variables in the [`Module`]. /// Get an iterator to the variables in the [`Module`].
#[inline] #[inline]
pub fn iter_var(&self) -> impl Iterator<Item = (&str, &Dynamic)> { pub fn iter_var(&self) -> impl Iterator<Item = (&str, &Dynamic)> {
self.variables self.variables
.iter() .as_deref()
.flat_map(|m| m.iter().map(|(k, v)| (k.as_str(), v))) .into_iter()
.flatten()
.map(|(k, v)| (k.as_str(), v))
} }
/// Get an iterator to the functions in the [`Module`]. /// Get an iterator to the functions in the [`Module`].
@ -2065,7 +2101,7 @@ impl Module {
let mut module = Module::new(); let mut module = Module::new();
// Extra modules left become sub-modules // Extra modules left become sub-modules
let mut imports = StaticVec::new_const(); let mut imports = crate::StaticVec::new_const();
if result.is_ok() { if result.is_ok() {
global global
@ -2088,7 +2124,8 @@ impl Module {
global.source = orig_source; global.source = orig_source;
result?; // The return value is thrown away and not used
let _ = result?;
// Variables with an alias left in the scope become module variables // Variables with an alias left in the scope become module variables
for (_name, value, mut aliases) in scope { for (_name, value, mut aliases) in scope {
@ -2223,7 +2260,7 @@ impl Module {
} }
// Index all Rust functions // Index all Rust functions
for (&hash, f) in module.functions.iter().flat_map(|m| m.iter()) { for (&hash, f) in module.functions.iter().flatten() {
match f.metadata.namespace { match f.metadata.namespace {
FnNamespace::Global => { FnNamespace::Global => {
// Flatten all functions with global namespace // Flatten all functions with global namespace
@ -2260,11 +2297,11 @@ impl Module {
if !self.is_indexed() { if !self.is_indexed() {
let mut path = Vec::with_capacity(4); let mut path = Vec::with_capacity(4);
let mut variables = StraightHashMap::with_capacity_and_hasher( let mut variables = StraightHashMap::with_capacity_and_hasher(
self.variables.as_ref().map_or(0, |m| m.len()), self.variables.as_deref().map_or(0, BTreeMap::len),
Default::default(), Default::default(),
); );
let mut functions = StraightHashMap::with_capacity_and_hasher( let mut functions = StraightHashMap::with_capacity_and_hasher(
self.functions.as_ref().map_or(0, |m| m.len()), self.functions.as_ref().map_or(0, StraightHashMap::len),
Default::default(), Default::default(),
); );
let mut type_iterators = BTreeMap::new(); let mut type_iterators = BTreeMap::new();

View File

@ -170,7 +170,7 @@ impl FileModuleResolver {
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn base_path(&self) -> Option<&Path> { pub fn base_path(&self) -> Option<&Path> {
self.base_path.as_ref().map(<_>::as_ref) self.base_path.as_deref()
} }
/// Set the base path for script files. /// Set the base path for script files.
#[inline(always)] #[inline(always)]

View File

@ -24,6 +24,9 @@ use std::{
mem, mem,
}; };
/// Standard not operator.
const OP_NOT: &str = Token::Bang.literal_syntax();
/// Level of optimization performed. /// Level of optimization performed.
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)] #[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
#[non_exhaustive] #[non_exhaustive]
@ -65,18 +68,19 @@ struct OptimizerState<'a> {
} }
impl<'a> OptimizerState<'a> { impl<'a> OptimizerState<'a> {
/// Create a new State. /// Create a new [`OptimizerState`].
#[inline(always)] #[inline(always)]
pub fn new( pub fn new(
engine: &'a Engine, engine: &'a Engine,
#[cfg(not(feature = "no_function"))] lib: &'a [crate::SharedModule], lib: &'a [crate::SharedModule],
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> Self { ) -> Self {
let mut _global = GlobalRuntimeState::new(engine); let mut _global = GlobalRuntimeState::new(engine);
let _lib = lib;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
{ {
_global.lib = lib.iter().cloned().collect(); _global.lib = _lib.iter().cloned().collect();
} }
Self { Self {
@ -138,7 +142,7 @@ impl<'a> OptimizerState<'a> {
pub fn call_fn_with_constant_arguments( pub fn call_fn_with_constant_arguments(
&mut self, &mut self,
fn_name: &str, fn_name: &str,
op_token: Option<&Token>, op_token: Token,
arg_values: &mut [Dynamic], arg_values: &mut [Dynamic],
) -> Dynamic { ) -> Dynamic {
self.engine self.engine
@ -156,39 +160,6 @@ impl<'a> OptimizerState<'a> {
} }
} }
// Has a system function a Rust-native override?
fn has_native_fn_override(
engine: &Engine,
hash_script: u64,
arg_types: impl AsRef<[TypeId]>,
) -> bool {
let hash = calc_fn_hash_full(hash_script, arg_types.as_ref().iter().copied());
// First check the global namespace and packages, but skip modules that are standard because
// they should never conflict with system functions.
if engine
.global_modules
.iter()
.filter(|m| !m.flags.contains(ModuleFlags::STANDARD_LIB))
.any(|m| m.contains_fn(hash))
{
return true;
}
// Then check sub-modules
#[cfg(not(feature = "no_module"))]
if engine
.global_sub_modules
.iter()
.flat_map(|m| m.values())
.any(|m| m.contains_qualified_fn(hash))
{
return true;
}
false
}
/// Optimize a block of [statements][Stmt]. /// Optimize a block of [statements][Stmt].
fn optimize_stmt_block( fn optimize_stmt_block(
mut statements: StmtBlockContainer, mut statements: StmtBlockContainer,
@ -1019,7 +990,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
// `` // ``
Expr::InterpolatedString(x, pos) if x.is_empty() => { Expr::InterpolatedString(x, pos) if x.is_empty() => {
state.set_dirty(); state.set_dirty();
*expr = Expr::StringConstant(state.engine.get_interned_string(""), *pos); *expr = Expr::StringConstant(state.engine.const_empty_string(), *pos);
} }
// `... ${const} ...` // `... ${const} ...`
Expr::InterpolatedString(..) if expr.is_constant() => { Expr::InterpolatedString(..) if expr.is_constant() => {
@ -1097,6 +1068,20 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
*expr = mem::take(&mut x.lhs); *expr = mem::take(&mut x.lhs);
}, },
// !true or !false
Expr::FnCall(x,..)
if x.name == OP_NOT
&& x.args.len() == 1
&& matches!(x.args[0], Expr::BoolConstant(..))
=> {
state.set_dirty();
if let Expr::BoolConstant(b, pos) = x.args[0] {
*expr = Expr::BoolConstant(!b, pos)
} else {
unreachable!()
}
}
// eval! // eval!
Expr::FnCall(x, ..) if x.name == KEYWORD_EVAL => { Expr::FnCall(x, ..) if x.name == KEYWORD_EVAL => {
state.propagate_constants = false; state.propagate_constants = false;
@ -1150,10 +1135,14 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
return; return;
} }
// Overloaded operators can override built-in. // Overloaded operators can override built-in.
_ if x.args.len() == 2 && x.op_token.is_some() && (state.engine.fast_operators() || !has_native_fn_override(state.engine, x.hashes.native(), &arg_types)) => { _ if x.args.len() == 2 && x.op_token != Token::NONE && (state.engine.fast_operators() || !state.engine.has_native_fn_override(x.hashes.native(), &arg_types)) => {
if let Some(result) = get_builtin_binary_op_fn(x.op_token.as_ref().unwrap(), &arg_values[0], &arg_values[1]) if let Some(result) = get_builtin_binary_op_fn(x.op_token.clone(), &arg_values[0], &arg_values[1])
.and_then(|f| { .and_then(|(f, ctx)| {
let context = (state.engine, x.name.as_str(),None, &state.global, *pos).into(); let context = if ctx {
Some((state.engine, x.name.as_str(),None, &state.global, *pos).into())
} else {
None
};
let (first, second) = arg_values.split_first_mut().unwrap(); let (first, second) = arg_values.split_first_mut().unwrap();
(f)(context, &mut [ first, &mut second[0] ]).ok() (f)(context, &mut [ first, &mut second[0] ]).ok()
}) { }) {
@ -1204,7 +1193,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
KEYWORD_TYPE_OF if arg_values.len() == 1 => state.engine.map_type_name(arg_values[0].type_name()).into(), KEYWORD_TYPE_OF if arg_values.len() == 1 => state.engine.map_type_name(arg_values[0].type_name()).into(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
crate::engine::KEYWORD_IS_SHARED if arg_values.len() == 1 => Dynamic::FALSE, crate::engine::KEYWORD_IS_SHARED if arg_values.len() == 1 => Dynamic::FALSE,
_ => state.call_fn_with_constant_arguments(&x.name, x.op_token.as_ref(), arg_values) _ => state.call_fn_with_constant_arguments(&x.name, x.op_token.clone(), arg_values)
}; };
if !result.is_null() { if !result.is_null() {
@ -1260,124 +1249,143 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
} }
} }
/// Optimize a block of [statements][Stmt] at top level. impl Engine {
/// /// Has a system function a Rust-native override?
/// Constants and variables from the scope are added. fn has_native_fn_override(&self, hash_script: u64, arg_types: impl AsRef<[TypeId]>) -> bool {
fn optimize_top_level( let hash = calc_fn_hash_full(hash_script, arg_types.as_ref().iter().copied());
statements: StmtBlockContainer,
engine: &Engine, // First check the global namespace and packages, but skip modules that are standard because
scope: &Scope, // they should never conflict with system functions.
#[cfg(not(feature = "no_function"))] lib: &[crate::SharedModule], if self
optimization_level: OptimizationLevel, .global_modules
) -> StmtBlockContainer { .iter()
let mut statements = statements; .filter(|m| !m.flags.contains(ModuleFlags::STANDARD_LIB))
.any(|m| m.contains_fn(hash))
{
return true;
}
// Then check sub-modules
#[cfg(not(feature = "no_module"))]
if self
.global_sub_modules
.as_deref()
.into_iter()
.flatten()
.any(|(_, m)| m.contains_qualified_fn(hash))
{
return true;
}
false
}
/// Optimize a block of [statements][Stmt] at top level.
///
/// Constants and variables from the scope are added.
fn optimize_top_level(
&self,
statements: StmtBlockContainer,
scope: &Scope,
lib: &[crate::SharedModule],
optimization_level: OptimizationLevel,
) -> StmtBlockContainer {
let mut statements = statements;
// If optimization level is None then skip optimizing
if optimization_level == OptimizationLevel::None {
statements.shrink_to_fit();
return statements;
}
// Set up the state
let mut state = OptimizerState::new(self, lib, optimization_level);
// Add constants from global modules
for (name, value) in self.global_modules.iter().rev().flat_map(|m| m.iter_var()) {
state.push_var(name, AccessMode::ReadOnly, value.clone());
}
// Add constants and variables from the scope
for (name, constant, value) in scope.iter() {
if constant {
state.push_var(name, AccessMode::ReadOnly, value);
} else {
state.push_var(name, AccessMode::ReadWrite, Dynamic::NULL);
}
}
optimize_stmt_block(statements, &mut state, true, false, true)
}
/// Optimize a collection of statements and functions into an [`AST`].
pub(crate) fn optimize_into_ast(
&self,
scope: &Scope,
statements: StmtBlockContainer,
#[cfg(not(feature = "no_function"))] functions: StaticVec<
crate::Shared<crate::ast::ScriptFnDef>,
>,
optimization_level: OptimizationLevel,
) -> AST {
let mut statements = statements;
#[cfg(not(feature = "no_function"))]
let lib: crate::Shared<_> = {
let mut module = crate::Module::new();
if optimization_level != OptimizationLevel::None {
// We only need the script library's signatures for optimization purposes
let mut lib2 = crate::Module::new();
for fn_def in &functions {
lib2.set_script_fn(crate::ast::ScriptFnDef {
name: fn_def.name.clone(),
access: fn_def.access,
body: crate::ast::StmtBlock::NONE,
params: fn_def.params.clone(),
#[cfg(not(feature = "no_module"))]
environ: None,
#[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")]
comments: Box::default(),
});
}
let lib2 = &[lib2.into()];
for fn_def in functions {
let mut fn_def = crate::func::shared_take_or_clone(fn_def);
// Optimize the function body
let body = mem::take(&mut *fn_def.body);
*fn_def.body = self.optimize_top_level(body, scope, lib2, optimization_level);
module.set_script_fn(fn_def);
}
} else {
for fn_def in functions {
module.set_script_fn(fn_def);
}
}
module.into()
};
#[cfg(feature = "no_function")]
let lib: crate::Shared<_> = crate::Module::new().into();
// If optimization level is None then skip optimizing
if optimization_level == OptimizationLevel::None {
statements.shrink_to_fit(); statements.shrink_to_fit();
return statements;
AST::new(
match optimization_level {
OptimizationLevel::None => statements,
OptimizationLevel::Simple | OptimizationLevel::Full => {
self.optimize_top_level(statements, scope, &[lib.clone()], optimization_level)
}
},
#[cfg(not(feature = "no_function"))]
lib,
)
} }
// Set up the state
let mut state = OptimizerState::new(
engine,
#[cfg(not(feature = "no_function"))]
lib,
optimization_level,
);
// Add constants from global modules
for (name, value) in engine
.global_modules
.iter()
.rev()
.flat_map(|m| m.iter_var())
{
state.push_var(name, AccessMode::ReadOnly, value.clone());
}
// Add constants and variables from the scope
for (name, constant, value) in scope.iter() {
if constant {
state.push_var(name, AccessMode::ReadOnly, value);
} else {
state.push_var(name, AccessMode::ReadWrite, Dynamic::NULL);
}
}
optimize_stmt_block(statements, &mut state, true, false, true)
}
/// Optimize an [`AST`].
pub fn optimize_into_ast(
engine: &Engine,
scope: &Scope,
statements: StmtBlockContainer,
#[cfg(not(feature = "no_function"))] functions: StaticVec<
crate::Shared<crate::ast::ScriptFnDef>,
>,
optimization_level: OptimizationLevel,
) -> AST {
let mut statements = statements;
#[cfg(not(feature = "no_function"))]
let lib: crate::Shared<_> = {
let mut module = crate::Module::new();
if optimization_level != OptimizationLevel::None {
// We only need the script library's signatures for optimization purposes
let mut lib2 = crate::Module::new();
for fn_def in &functions {
lib2.set_script_fn(crate::ast::ScriptFnDef {
name: fn_def.name.clone(),
access: fn_def.access,
body: crate::ast::StmtBlock::NONE,
params: fn_def.params.clone(),
#[cfg(not(feature = "no_module"))]
environ: None,
#[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")]
comments: Box::default(),
});
}
let lib2 = &[lib2.into()];
for fn_def in functions {
let mut fn_def = crate::func::shared_take_or_clone(fn_def);
// Optimize the function body
let body = mem::take(&mut *fn_def.body);
*fn_def.body = optimize_top_level(body, engine, scope, lib2, optimization_level);
module.set_script_fn(fn_def);
}
} else {
for fn_def in functions {
module.set_script_fn(fn_def);
}
}
module.into()
};
statements.shrink_to_fit();
AST::new(
match optimization_level {
OptimizationLevel::None => statements,
OptimizationLevel::Simple | OptimizationLevel::Full => optimize_top_level(
statements,
engine,
scope,
#[cfg(not(feature = "no_function"))]
&[lib.clone()],
optimization_level,
),
},
#[cfg(not(feature = "no_function"))]
lib,
)
} }

View File

@ -261,8 +261,7 @@ pub mod array_functions {
m1 += m2; m1 += m2;
s1 += s2; s1 += s2;
_ctx.engine() _ctx.engine().throw_on_size((a1, m1, s1))?;
.raise_err_if_over_data_size_limit((a1, m1, s1))?;
guard.push(item.clone()); guard.push(item.clone());
arr_len += 1; arr_len += 1;

View File

@ -81,8 +81,7 @@ pub mod blob_functions {
// Check if blob will be over max size limit // Check if blob will be over max size limit
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
_ctx.engine() _ctx.engine().throw_on_size((len, 0, 0))?;
.raise_err_if_over_data_size_limit((len, 0, 0))?;
let mut blob = Blob::new(); let mut blob = Blob::new();
blob.resize(len, (value & 0x0000_00ff) as u8); blob.resize(len, (value & 0x0000_00ff) as u8);

View File

@ -235,18 +235,18 @@ macro_rules! reg_range {
concat!("from: ", stringify!($y)), concat!("from: ", stringify!($y)),
concat!("to: ", stringify!($y)), concat!("to: ", stringify!($y)),
concat!("Iterator<", stringify!($y), ">"), concat!("Iterator<", stringify!($y), ">"),
], [ ], ["\
"/// Return an iterator over the exclusive range of `from..to`.", /// Return an iterator over the exclusive range of `from..to`.\n\
"/// The value `to` is never included.", /// The value `to` is never included.\n\
"///", ///\n\
"/// # Example", /// # Example\n\
"///", ///\n\
"/// ```rhai", /// ```rhai\n\
"/// // prints all values from 8 to 17", /// // prints all values from 8 to 17\n\
"/// for n in range(8, 18) {", /// for n in range(8, 18) {\n\
"/// print(n);", /// print(n);\n\
"/// }", /// }\n\
"/// ```" /// ```"
]); ]);
$lib.set_iterator::<RangeInclusive<$y>>(); $lib.set_iterator::<RangeInclusive<$y>>();
@ -269,27 +269,27 @@ macro_rules! reg_range {
concat!("to: ", stringify!($y)), concat!("to: ", stringify!($y)),
concat!("step: ", stringify!($y)), concat!("step: ", stringify!($y)),
concat!("Iterator<", stringify!($y), ">") concat!("Iterator<", stringify!($y), ">")
], [ ], ["\
"/// Return an iterator over the exclusive range of `from..to`, each iteration increasing by `step`.", /// Return an iterator over the exclusive range of `from..to`, each iteration increasing by `step`.\n\
"/// The value `to` is never included.", /// The value `to` is never included.\n\
"///", ///\n\
"/// If `from` > `to` and `step` < 0, iteration goes backwards.", /// If `from` > `to` and `step` < 0, iteration goes backwards.\n\
"///", ///\n\
"/// If `from` > `to` and `step` > 0 or `from` < `to` and `step` < 0, an empty iterator is returned.", /// If `from` > `to` and `step` > 0 or `from` < `to` and `step` < 0, an empty iterator is returned.\n\
"///", ///\n\
"/// # Example", /// # Example\n\
"///", ///\n\
"/// ```rhai", /// ```rhai\n\
"/// // prints all values from 8 to 17 in steps of 3", /// // prints all values from 8 to 17 in steps of 3\n\
"/// for n in range(8, 18, 3) {", /// for n in range(8, 18, 3) {\n\
"/// print(n);", /// print(n);\n\
"/// }", /// }\n\
"///", ///\n\
"/// // prints all values down from 18 to 9 in steps of -3", /// // prints all values down from 18 to 9 in steps of -3\n\
"/// for n in range(18, 8, -3) {", /// for n in range(18, 8, -3) {\n\
"/// print(n);", /// print(n);\n\
"/// }", /// }\n\
"/// ```" /// ```"
]); ]);
let _hash = $lib.set_native_fn($x, |range: std::ops::Range<$y>, step: $y| StepRange::new(range.start, range.end, step, $add)); let _hash = $lib.set_native_fn($x, |range: std::ops::Range<$y>, step: $y| StepRange::new(range.start, range.end, step, $add));
@ -299,26 +299,26 @@ macro_rules! reg_range {
concat!("range: Range<", stringify!($y), ">"), concat!("range: Range<", stringify!($y), ">"),
concat!("step: ", stringify!($y)), concat!("step: ", stringify!($y)),
concat!("Iterator<", stringify!($y), ">") concat!("Iterator<", stringify!($y), ">")
], [ ], ["\
"/// Return an iterator over an exclusive range, each iteration increasing by `step`.", /// Return an iterator over an exclusive range, each iteration increasing by `step`.\n\
"///", ///\n\
"/// If `range` is reversed and `step` < 0, iteration goes backwards.", /// If `range` is reversed and `step` < 0, iteration goes backwards.\n\
"///", ///\n\
"/// Otherwise, if `range` is empty, an empty iterator is returned.", /// Otherwise, if `range` is empty, an empty iterator is returned.\n\
"///", ///\n\
"/// # Example", /// # Example\n\
"///", ///\n\
"/// ```rhai", /// ```rhai\n\
"/// // prints all values from 8 to 17 in steps of 3", /// // prints all values from 8 to 17 in steps of 3\n\
"/// for n in range(8..18, 3) {", /// for n in range(8..18, 3) {\n\
"/// print(n);", /// print(n);\n\
"/// }", /// }\n\
"///", ///\n\
"/// // prints all values down from 18 to 9 in steps of -3", /// // prints all values down from 18 to 9 in steps of -3\n\
"/// for n in range(18..8, -3) {", /// for n in range(18..8, -3) {\n\
"/// print(n);", /// print(n);\n\
"/// }", /// }\n\
"/// ```" /// ```"
]); ]);
)* )*
}; };
@ -665,6 +665,7 @@ mod range_functions {
} }
/// Return true if the range contains no items. /// Return true if the range contains no items.
#[rhai_fn(get = "is_empty", name = "is_empty", pure)] #[rhai_fn(get = "is_empty", name = "is_empty", pure)]
#[allow(unstable_name_collisions)]
pub fn is_empty_exclusive(range: &mut ExclusiveRange) -> bool { pub fn is_empty_exclusive(range: &mut ExclusiveRange) -> bool {
range.is_empty() range.is_empty()
} }

View File

@ -220,7 +220,7 @@ fn collect_fn_metadata(
"comments".into(), "comments".into(),
func.comments func.comments
.iter() .iter()
.map(|s| engine.get_interned_string(s.as_ref()).into()) .map(|s| engine.get_interned_string(s.as_str()).into())
.collect::<Array>() .collect::<Array>()
.into(), .into(),
); );
@ -267,9 +267,10 @@ fn collect_fn_metadata(
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
ctx.engine() ctx.engine()
.global_sub_modules .global_sub_modules
.iter() .as_deref()
.flat_map(|m| m.values()) .into_iter()
.flat_map(|m| m.iter_script_fn()) .flatten()
.flat_map(|(_, m)| m.iter_script_fn())
.filter(|(ns, a, n, p, f)| filter(*ns, *a, n, *p, f)) .filter(|(ns, a, n, p, f)| filter(*ns, *a, n, *p, f))
.for_each(|(.., f)| { .for_each(|(.., f)| {
list.push( list.push(

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,6 @@
/// * `reify!(`_variable_ or _expression_`,|`_temp-variable_`: `_type_`|` _code_ `)` /// * `reify!(`_variable_ or _expression_`,|`_temp-variable_`: `_type_`|` _code_ `)`
/// * `reify!(`_variable_ or _expression_ `=>` `Option<`_type_`>` `)` /// * `reify!(`_variable_ or _expression_ `=>` `Option<`_type_`>` `)`
/// * `reify!(`_variable_ or _expression_ `=>` _type_ `)` /// * `reify!(`_variable_ or _expression_ `=>` _type_ `)`
#[macro_export]
macro_rules! reify { macro_rules! reify {
($old:ident, |$new:ident : $t:ty| $code:expr, || $fallback:expr) => {{ ($old:ident, |$new:ident : $t:ty| $code:expr, || $fallback:expr) => {{
#[allow(clippy::redundant_else)] #[allow(clippy::redundant_else)]

View File

@ -168,10 +168,12 @@ pub fn gen_metadata_to_json(
include_standard_packages: bool, include_standard_packages: bool,
) -> serde_json::Result<String> { ) -> serde_json::Result<String> {
let _ast = ast; let _ast = ast;
#[cfg(feature = "metadata")]
let mut global_doc = String::new();
let mut global = ModuleMetadata::new(); let mut global = ModuleMetadata::new();
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
for (name, m) in engine.global_sub_modules.iter().flat_map(|m| m.iter()) { for (name, m) in engine.global_sub_modules.as_deref().into_iter().flatten() {
global.modules.insert(name, m.as_ref().into()); global.modules.insert(name, m.as_ref().into());
} }
@ -185,7 +187,16 @@ pub fn gen_metadata_to_json(
.global_modules .global_modules
.iter() .iter()
.filter(|m| !m.flags.contains(exclude_flags)) .filter(|m| !m.flags.contains(exclude_flags))
.flat_map(|m| m.iter_fn()) .flat_map(|m| {
#[cfg(feature = "metadata")]
if !m.doc().is_empty() {
if !global_doc.is_empty() {
global_doc.push('\n');
}
global_doc.push_str(m.doc());
}
m.iter_fn()
})
.for_each(|f| { .for_each(|f| {
#[allow(unused_mut)] #[allow(unused_mut)]
let mut meta: FnMetadata = f.into(); let mut meta: FnMetadata = f.into();
@ -213,7 +224,17 @@ pub fn gen_metadata_to_json(
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
if let Some(ast) = _ast { if let Some(ast) = _ast {
global.doc = ast.doc(); if !ast.doc().is_empty() {
if !global_doc.is_empty() {
global_doc.push('\n');
}
global_doc.push_str(ast.doc());
}
}
#[cfg(feature = "metadata")]
{
global.doc = &global_doc;
} }
serde_json::to_string_pretty(&global) serde_json::to_string_pretty(&global)

View File

@ -5,7 +5,7 @@ use crate::engine::{
KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
}; };
use crate::func::native::OnParseTokenCallback; use crate::func::native::OnParseTokenCallback;
use crate::{Engine, Identifier, LexError, SmartString, StaticVec, INT, UNSIGNED_INT}; use crate::{Engine, Identifier, LexError, Position, SmartString, StaticVec, INT, UNSIGNED_INT};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{ use std::{
@ -13,7 +13,6 @@ use std::{
char, fmt, char, fmt,
iter::{FusedIterator, Peekable}, iter::{FusedIterator, Peekable},
num::NonZeroUsize, num::NonZeroUsize,
ops::{Add, AddAssign},
rc::Rc, rc::Rc,
str::{Chars, FromStr}, str::{Chars, FromStr},
}; };
@ -24,9 +23,9 @@ pub struct TokenizerControlBlock {
/// Is the current tokenizer position within an interpolated text string? /// Is the current tokenizer position within an interpolated text string?
/// This flag allows switching the tokenizer back to _text_ parsing after an interpolation stream. /// This flag allows switching the tokenizer back to _text_ parsing after an interpolation stream.
pub is_within_text: bool, pub is_within_text: bool,
/// Collection of global comments. /// Global comments.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
pub global_comments: Vec<SmartString>, pub global_comments: String,
} }
impl TokenizerControlBlock { impl TokenizerControlBlock {
@ -37,7 +36,7 @@ impl TokenizerControlBlock {
Self { Self {
is_within_text: false, is_within_text: false,
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
global_comments: Vec::new(), global_comments: String::new(),
} }
} }
} }
@ -50,326 +49,12 @@ type LERR = LexError;
/// Separator character for numbers. /// Separator character for numbers.
const NUMBER_SEPARATOR: char = '_'; const NUMBER_SEPARATOR: char = '_';
/// No token.
pub const NO_TOKEN: Token = Token::NONE;
/// A stream of tokens. /// A stream of tokens.
pub type TokenStream<'a> = Peekable<TokenIterator<'a>>; pub type TokenStream<'a> = Peekable<TokenIterator<'a>>;
/// A location (line number + character position) in the input script.
///
/// # Limitations
///
/// In order to keep footprint small, both line number and character position have 16-bit resolution,
/// meaning they go up to a maximum of 65,535 lines and 65,535 characters per line.
///
/// Advancing beyond the maximum line length or maximum number of lines is not an error but has no effect.
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
pub struct Position {
/// Line number: 0 = none
#[cfg(not(feature = "no_position"))]
line: u16,
/// Character position: 0 = BOL
#[cfg(not(feature = "no_position"))]
pos: u16,
}
impl Position {
/// A [`Position`] representing no position.
pub const NONE: Self = Self {
#[cfg(not(feature = "no_position"))]
line: 0,
#[cfg(not(feature = "no_position"))]
pos: 0,
};
/// A [`Position`] representing the first position.
pub const START: Self = Self {
#[cfg(not(feature = "no_position"))]
line: 1,
#[cfg(not(feature = "no_position"))]
pos: 0,
};
/// Create a new [`Position`].
///
/// `line` must not be zero.
///
/// If `position` is zero, then it is at the beginning of a line.
///
/// # Panics
///
/// Panics if `line` is zero.
#[inline]
#[must_use]
pub const fn new(line: u16, position: u16) -> Self {
assert!(line != 0, "line cannot be zero");
let _pos = position;
Self {
#[cfg(not(feature = "no_position"))]
line,
#[cfg(not(feature = "no_position"))]
pos: _pos,
}
}
/// Get the line number (1-based), or [`None`] if there is no position.
#[inline]
#[must_use]
pub const fn line(self) -> Option<usize> {
#[cfg(not(feature = "no_position"))]
return if self.is_none() {
None
} else {
Some(self.line as usize)
};
#[cfg(feature = "no_position")]
return None;
}
/// Get the character position (1-based), or [`None`] if at beginning of a line.
#[inline]
#[must_use]
pub const fn position(self) -> Option<usize> {
#[cfg(not(feature = "no_position"))]
return if self.is_none() || self.pos == 0 {
None
} else {
Some(self.pos as usize)
};
#[cfg(feature = "no_position")]
return None;
}
/// Advance by one character position.
#[inline]
pub(crate) fn advance(&mut self) {
#[cfg(not(feature = "no_position"))]
{
assert!(!self.is_none(), "cannot advance Position::none");
// Advance up to maximum position
if self.pos < u16::MAX {
self.pos += 1;
}
}
}
/// Go backwards by one character position.
///
/// # Panics
///
/// Panics if already at beginning of a line - cannot rewind to a previous line.
#[inline]
pub(crate) fn rewind(&mut self) {
#[cfg(not(feature = "no_position"))]
{
assert!(!self.is_none(), "cannot rewind Position::none");
assert!(self.pos > 0, "cannot rewind at position 0");
self.pos -= 1;
}
}
/// Advance to the next line.
#[inline]
pub(crate) fn new_line(&mut self) {
#[cfg(not(feature = "no_position"))]
{
assert!(!self.is_none(), "cannot advance Position::none");
// Advance up to maximum position
if self.line < u16::MAX {
self.line += 1;
self.pos = 0;
}
}
}
/// Is this [`Position`] at the beginning of a line?
#[inline]
#[must_use]
pub const fn is_beginning_of_line(self) -> bool {
#[cfg(not(feature = "no_position"))]
return self.pos == 0 && !self.is_none();
#[cfg(feature = "no_position")]
return false;
}
/// Is there no [`Position`]?
#[inline]
#[must_use]
pub const fn is_none(self) -> bool {
#[cfg(not(feature = "no_position"))]
return self.line == 0 && self.pos == 0;
#[cfg(feature = "no_position")]
return true;
}
/// Returns an fallback [`Position`] if it is [`NONE`][Position::NONE]?
#[inline]
#[must_use]
pub const fn or_else(self, pos: Self) -> Self {
if self.is_none() {
pos
} else {
self
}
}
/// Print this [`Position`] for debug purposes.
#[inline]
pub(crate) fn debug_print(self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.is_none() {
write!(_f, " @ {:?}", self)?;
}
Ok(())
}
}
impl Default for Position {
#[inline(always)]
#[must_use]
fn default() -> Self {
Self::START
}
}
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_none() {
write!(f, "none")?;
} else {
#[cfg(not(feature = "no_position"))]
write!(f, "line {}, position {}", self.line, self.pos)?;
#[cfg(feature = "no_position")]
unreachable!("no position");
}
Ok(())
}
}
impl fmt::Debug for Position {
#[cold]
#[inline(never)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_none() {
f.write_str("none")
} else {
#[cfg(not(feature = "no_position"))]
if self.is_beginning_of_line() {
write!(f, "{}", self.line)
} else {
write!(f, "{}:{}", self.line, self.pos)
}
#[cfg(feature = "no_position")]
unreachable!("no position");
}
}
}
impl Add for Position {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
if rhs.is_none() {
self
} else {
#[cfg(not(feature = "no_position"))]
return Self {
line: self.line + rhs.line - 1,
pos: if rhs.is_beginning_of_line() {
self.pos
} else {
self.pos + rhs.pos - 1
},
};
#[cfg(feature = "no_position")]
unreachable!("no position");
}
}
}
impl AddAssign for Position {
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
}
}
/// _(internals)_ A span consisting of a starting and an ending [positions][Position].
/// Exported under the `internals` feature only.
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
pub struct Span {
/// Starting [position][Position].
start: Position,
/// Ending [position][Position].
end: Position,
}
impl Default for Span {
#[inline(always)]
#[must_use]
fn default() -> Self {
Self::NONE
}
}
impl Span {
/// Empty [`Span`].
pub const NONE: Self = Self::new(Position::NONE, Position::NONE);
/// Create a new [`Span`].
#[inline(always)]
#[must_use]
pub const fn new(start: Position, end: Position) -> Self {
Self { start, end }
}
/// Is this [`Span`] non-existent?
#[inline]
#[must_use]
pub const fn is_none(&self) -> bool {
self.start.is_none() && self.end.is_none()
}
/// Get the [`Span`]'s starting [position][Position].
#[inline(always)]
#[must_use]
pub const fn start(&self) -> Position {
self.start
}
/// Get the [`Span`]'s ending [position][Position].
#[inline(always)]
#[must_use]
pub const fn end(&self) -> Position {
self.end
}
}
impl fmt::Display for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let _f = f;
#[cfg(not(feature = "no_position"))]
match (self.start(), self.end()) {
(Position::NONE, Position::NONE) => write!(_f, "{:?}", Position::NONE),
(Position::NONE, end) => write!(_f, "..{:?}", end),
(start, Position::NONE) => write!(_f, "{:?}", start),
(start, end) if start.line() != end.line() => {
write!(_f, "{:?}-{:?}", start, end)
}
(start, end) => write!(
_f,
"{}:{}-{}",
start.line().unwrap(),
start.position().unwrap_or(0),
end.position().unwrap_or(0)
),
}
#[cfg(feature = "no_position")]
Ok(())
}
}
impl fmt::Debug for Span {
#[cold]
#[inline(never)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
/// _(internals)_ A Rhai language token. /// _(internals)_ A Rhai language token.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[derive(Debug, PartialEq, Clone, Hash)] #[derive(Debug, PartialEq, Clone, Hash)]
@ -489,6 +174,8 @@ pub enum Token {
For, For,
/// `in` /// `in`
In, In,
/// `!in`
NotIn,
/// `<` /// `<`
LessThan, LessThan,
/// `>` /// `>`
@ -575,7 +262,7 @@ pub enum Token {
/// A lexer error. /// A lexer error.
LexError(Box<LexError>), LexError(Box<LexError>),
/// A comment block. /// A comment block.
Comment(Box<SmartString>), Comment(Box<String>),
/// A reserved symbol. /// A reserved symbol.
Reserved(Box<SmartString>), Reserved(Box<SmartString>),
/// A custom keyword. /// A custom keyword.
@ -584,7 +271,10 @@ pub enum Token {
#[cfg(not(feature = "no_custom_syntax"))] #[cfg(not(feature = "no_custom_syntax"))]
Custom(Box<SmartString>), Custom(Box<SmartString>),
/// End of the input stream. /// End of the input stream.
/// Used as a placeholder for the end of input.
EOF, EOF,
/// Placeholder to indicate the lack of a token.
NONE,
} }
impl fmt::Display for Token { impl fmt::Display for Token {
@ -610,6 +300,7 @@ impl fmt::Display for Token {
Comment(s) => f.write_str(s), Comment(s) => f.write_str(s),
EOF => f.write_str("{EOF}"), EOF => f.write_str("{EOF}"),
NONE => f.write_str("{NONE}"),
token => f.write_str(token.literal_syntax()), token => f.write_str(token.literal_syntax()),
} }
@ -638,7 +329,7 @@ impl Token {
Custom(..) => false, Custom(..) => false,
LexError(..) | Comment(..) => false, LexError(..) | Comment(..) => false,
EOF => false, EOF | NONE => false,
_ => true, _ => true,
} }
@ -696,6 +387,7 @@ impl Token {
Loop => "loop", Loop => "loop",
For => "for", For => "for",
In => "in", In => "in",
NotIn => "!in",
LessThan => "<", LessThan => "<",
GreaterThan => ">", GreaterThan => ">",
Bang => "!", Bang => "!",
@ -750,37 +442,43 @@ impl Token {
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn is_op_assignment(&self) -> bool { pub const fn is_op_assignment(&self) -> bool {
#[allow(clippy::enum_glob_use)]
use Token::*;
matches!( matches!(
self, self,
Self::PlusAssign PlusAssign
| Self::MinusAssign | MinusAssign
| Self::MultiplyAssign | MultiplyAssign
| Self::DivideAssign | DivideAssign
| Self::LeftShiftAssign | LeftShiftAssign
| Self::RightShiftAssign | RightShiftAssign
| Self::ModuloAssign | ModuloAssign
| Self::PowerOfAssign | PowerOfAssign
| Self::AndAssign | AndAssign
| Self::OrAssign | OrAssign
| Self::XOrAssign | XOrAssign
) )
} }
/// Get the corresponding operator of the token if it is an op-assignment operator. /// Get the corresponding operator of the token if it is an op-assignment operator.
#[must_use] #[must_use]
pub const fn get_base_op_from_assignment(&self) -> Option<Self> { pub const fn get_base_op_from_assignment(&self) -> Option<Self> {
#[allow(clippy::enum_glob_use)]
use Token::*;
Some(match self { Some(match self {
Self::PlusAssign => Self::Plus, PlusAssign => Plus,
Self::MinusAssign => Self::Minus, MinusAssign => Minus,
Self::MultiplyAssign => Self::Multiply, MultiplyAssign => Multiply,
Self::DivideAssign => Self::Divide, DivideAssign => Divide,
Self::LeftShiftAssign => Self::LeftShift, LeftShiftAssign => LeftShift,
Self::RightShiftAssign => Self::RightShift, RightShiftAssign => RightShift,
Self::ModuloAssign => Self::Modulo, ModuloAssign => Modulo,
Self::PowerOfAssign => Self::PowerOf, PowerOfAssign => PowerOf,
Self::AndAssign => Self::Ampersand, AndAssign => Ampersand,
Self::OrAssign => Self::Pipe, OrAssign => Pipe,
Self::XOrAssign => Self::XOr, XOrAssign => XOr,
_ => return None, _ => return None,
}) })
} }
@ -789,37 +487,42 @@ impl Token {
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn has_op_assignment(&self) -> bool { pub const fn has_op_assignment(&self) -> bool {
#[allow(clippy::enum_glob_use)]
use Token::*;
matches!( matches!(
self, self,
Self::Plus Plus | Minus
| Self::Minus | Multiply
| Self::Multiply | Divide
| Self::Divide | LeftShift
| Self::LeftShift | RightShift
| Self::RightShift | Modulo
| Self::Modulo | PowerOf
| Self::PowerOf | Ampersand
| Self::Ampersand | Pipe
| Self::Pipe | XOr
| Self::XOr
) )
} }
/// Get the corresponding op-assignment operator of the token. /// Get the corresponding op-assignment operator of the token.
#[must_use] #[must_use]
pub const fn convert_to_op_assignment(&self) -> Option<Self> { pub const fn convert_to_op_assignment(&self) -> Option<Self> {
#[allow(clippy::enum_glob_use)]
use Token::*;
Some(match self { Some(match self {
Self::Plus => Self::PlusAssign, Plus => PlusAssign,
Self::Minus => Self::MinusAssign, Minus => MinusAssign,
Self::Multiply => Self::MultiplyAssign, Multiply => MultiplyAssign,
Self::Divide => Self::DivideAssign, Divide => DivideAssign,
Self::LeftShift => Self::LeftShiftAssign, LeftShift => LeftShiftAssign,
Self::RightShift => Self::RightShiftAssign, RightShift => RightShiftAssign,
Self::Modulo => Self::ModuloAssign, Modulo => ModuloAssign,
Self::PowerOf => Self::PowerOfAssign, PowerOf => PowerOfAssign,
Self::Ampersand => Self::AndAssign, Ampersand => AndAssign,
Self::Pipe => Self::OrAssign, Pipe => OrAssign,
Self::XOr => Self::XOrAssign, XOr => XOrAssign,
_ => return None, _ => return None,
}) })
} }
@ -871,6 +574,7 @@ impl Token {
"loop" => Loop, "loop" => Loop,
"for" => For, "for" => For,
"in" => In, "in" => In,
"!in" => NotIn,
"<" => LessThan, "<" => LessThan,
">" => GreaterThan, ">" => GreaterThan,
"!" => Bang, "!" => Bang,
@ -956,13 +660,6 @@ impl Token {
} }
} }
/// Is this token [`EOF`][Token::EOF]?
#[inline(always)]
#[must_use]
pub const fn is_eof(&self) -> bool {
matches!(self, Self::EOF)
}
/// If another operator is after these, it's probably a unary operator /// If another operator is after these, it's probably a unary operator
/// (not sure about `fn` name). /// (not sure about `fn` name).
#[must_use] #[must_use]
@ -1018,6 +715,7 @@ impl Token {
While | While |
Until | Until |
In | In |
NotIn |
And | And |
AndAssign | AndAssign |
Or | Or |
@ -1049,7 +747,7 @@ impl Token {
EqualsTo | NotEqualsTo => 90, EqualsTo | NotEqualsTo => 90,
In => 110, In | NotIn => 110,
LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo => 130, LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo => 130,
@ -1170,7 +868,7 @@ impl From<Token> for String {
#[derive(Debug, Clone, Eq, PartialEq, Default)] #[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct TokenizeState { pub struct TokenizeState {
/// Maximum length of a string. /// Maximum length of a string.
pub max_string_size: Option<NonZeroUsize>, pub max_string_len: Option<NonZeroUsize>,
/// Can the next token be a unary operator? /// Can the next token be a unary operator?
pub next_token_cannot_be_unary: bool, pub next_token_cannot_be_unary: bool,
/// Shared object to allow controlling the tokenizer externally. /// Shared object to allow controlling the tokenizer externally.
@ -1197,6 +895,18 @@ pub trait InputStream {
fn peek_next(&mut self) -> Option<char>; fn peek_next(&mut self) -> Option<char>;
} }
/// Return error if the string is longer than the maximum length.
#[inline]
fn ensure_string_len_within_limit(max: Option<NonZeroUsize>, value: &str) -> Result<(), LexError> {
if let Some(max) = max {
if value.len() > max.get() {
return Err(LexError::StringTooLong(max.get()));
}
}
Ok(())
}
/// _(internals)_ Parse a string literal ended by a specified termination character. /// _(internals)_ Parse a string literal ended by a specified termination character.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
/// ///
@ -1286,11 +996,8 @@ pub fn parse_string_literal(
break; break;
} }
if let Some(max) = state.max_string_size { ensure_string_len_within_limit(state.max_string_len, &result)
if result.len() > max.get() { .map_err(|err| (err, start))?;
return Err((LexError::StringTooLong(max.get()), *pos));
}
}
// Close wrapper // Close wrapper
if termination_char == next_char && escape.is_empty() { if termination_char == next_char && escape.is_empty() {
@ -1425,11 +1132,7 @@ pub fn parse_string_literal(
} }
} }
if let Some(max) = state.max_string_size { ensure_string_len_within_limit(state.max_string_len, &result).map_err(|err| (err, start))?;
if result.len() > max.get() {
return Err((LexError::StringTooLong(max.get()), *pos));
}
}
Ok((result, interpolated, first_char)) Ok((result, interpolated, first_char))
} }
@ -1446,7 +1149,7 @@ fn scan_block_comment(
stream: &mut impl InputStream, stream: &mut impl InputStream,
level: usize, level: usize,
pos: &mut Position, pos: &mut Position,
comment: Option<&mut SmartString>, comment: Option<&mut String>,
) -> usize { ) -> usize {
let mut level = level; let mut level = level;
let mut comment = comment; let mut comment = comment;
@ -1541,7 +1244,7 @@ fn get_next_token_inner(
if state.comment_level > 0 { if state.comment_level > 0 {
let start_pos = *pos; let start_pos = *pos;
let mut comment = if state.include_comments { let mut comment = if state.include_comments {
Some(SmartString::new_const()) Some(String::new())
} else { } else {
None None
}; };
@ -1748,11 +1451,17 @@ fn get_next_token_inner(
// letter or underscore ... // letter or underscore ...
#[cfg(not(feature = "unicode-xid-ident"))] #[cfg(not(feature = "unicode-xid-ident"))]
('a'..='z' | '_' | 'A'..='Z', ..) => { ('a'..='z' | '_' | 'A'..='Z', ..) => {
return Some(get_token_as_identifier(stream, pos, start_pos, c)); return Some(
parse_identifier_token(stream, pos, start_pos, c)
.unwrap_or_else(|err| (Token::LexError(err.into()), start_pos)),
);
} }
#[cfg(feature = "unicode-xid-ident")] #[cfg(feature = "unicode-xid-ident")]
(ch, ..) if unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_' => { (ch, ..) if unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_' => {
return Some(get_token_as_identifier(stream, pos, start_pos, c)); return Some(
parse_identifier_token(stream, pos, start_pos, c)
.unwrap_or_else(|err| (Token::LexError(err.into()), start_pos)),
);
} }
// " - string literal // " - string literal
@ -1928,7 +1637,7 @@ fn get_next_token_inner(
('/', '/') => { ('/', '/') => {
eat_next(stream, pos); eat_next(stream, pos);
let mut comment: Option<SmartString> = match stream.peek_next() { let mut comment: Option<String> = match stream.peek_next() {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
Some('/') => { Some('/') => {
@ -1971,11 +1680,13 @@ fn get_next_token_inner(
if let Some(comment) = comment { if let Some(comment) = comment {
match comment { match comment {
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
_ if comment.starts_with("//!") => state _ if comment.starts_with("//!") => {
.tokenizer_control let g = &mut state.tokenizer_control.borrow_mut().global_comments;
.borrow_mut() if !g.is_empty() {
.global_comments g.push('\n');
.push(comment), }
g.push_str(&comment);
}
_ => return Some((Token::Comment(comment.into()), start_pos)), _ => return Some((Token::Comment(comment.into()), start_pos)),
} }
} }
@ -1984,7 +1695,7 @@ fn get_next_token_inner(
state.comment_level = 1; state.comment_level = 1;
eat_next(stream, pos); eat_next(stream, pos);
let mut comment: Option<SmartString> = match stream.peek_next() { let mut comment: Option<String> = match stream.peek_next() {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
Some('*') => { Some('*') => {
@ -2119,6 +1830,15 @@ fn get_next_token_inner(
} }
('>', ..) => return Some((Token::GreaterThan, start_pos)), ('>', ..) => return Some((Token::GreaterThan, start_pos)),
('!', 'i') => {
eat_next(stream, pos);
if stream.peek_next() == Some('n') {
eat_next(stream, pos);
return Some((Token::NotIn, start_pos));
}
stream.unget('i');
return Some((Token::Bang, start_pos));
}
('!', '=') => { ('!', '=') => {
eat_next(stream, pos); eat_next(stream, pos);
@ -2220,12 +1940,12 @@ fn get_next_token_inner(
} }
/// Get the next token, parsing it as an identifier. /// Get the next token, parsing it as an identifier.
fn get_token_as_identifier( fn parse_identifier_token(
stream: &mut impl InputStream, stream: &mut impl InputStream,
pos: &mut Position, pos: &mut Position,
start_pos: Position, start_pos: Position,
first_char: char, first_char: char,
) -> (Token, Position) { ) -> Result<(Token, Position), LexError> {
let mut identifier = SmartString::new_const(); let mut identifier = SmartString::new_const();
identifier.push(first_char); identifier.push(first_char);
@ -2240,19 +1960,20 @@ fn get_token_as_identifier(
} }
if let Some(token) = Token::lookup_symbol_from_syntax(&identifier) { if let Some(token) = Token::lookup_symbol_from_syntax(&identifier) {
return (token, start_pos); return Ok((token, start_pos));
} else if Token::is_reserved_keyword(&identifier) { }
return (Token::Reserved(Box::new(identifier)), start_pos); if Token::is_reserved_keyword(&identifier) {
return Ok((Token::Reserved(Box::new(identifier)), start_pos));
} }
if !is_valid_identifier(&identifier) { if !is_valid_identifier(&identifier) {
return ( return Ok((
Token::LexError(LERR::MalformedIdentifier(identifier.to_string()).into()), Token::LexError(LERR::MalformedIdentifier(identifier.to_string()).into()),
start_pos, start_pos,
); ));
} }
(Token::Identifier(identifier.into()), start_pos) Ok((Token::Identifier(identifier.into()), start_pos))
} }
/// Is a keyword allowed as a function? /// Is a keyword allowed as a function?
@ -2435,7 +2156,7 @@ impl<'a> Iterator for TokenIterator<'a> {
Some((Token::Reserved(s), pos)) => (match Some((Token::Reserved(s), pos)) => (match
(s.as_str(), (s.as_str(),
#[cfg(not(feature = "no_custom_syntax"))] #[cfg(not(feature = "no_custom_syntax"))]
self.engine.custom_keywords.as_ref().map_or(false, |m| m.contains_key(&*s)), self.engine.custom_keywords.as_deref().map_or(false, |m| m.contains_key(&*s)),
#[cfg(feature = "no_custom_syntax")] #[cfg(feature = "no_custom_syntax")]
false false
) )
@ -2472,7 +2193,7 @@ impl<'a> Iterator for TokenIterator<'a> {
#[cfg(feature = "no_custom_syntax")] #[cfg(feature = "no_custom_syntax")]
(.., true) => unreachable!("no custom operators"), (.., true) => unreachable!("no custom operators"),
// Reserved keyword that is not custom and disabled. // Reserved keyword that is not custom and disabled.
(token, false) if self.engine.disabled_symbols.as_ref().map_or(false,|m| m.contains(token)) => { (token, false) if self.engine.disabled_symbols.as_deref().map_or(false,|m| m.contains(token)) => {
let msg = format!("reserved {} '{token}' is disabled", if is_valid_identifier(token) { "keyword"} else {"symbol"}); let msg = format!("reserved {} '{token}' is disabled", if is_valid_identifier(token) { "keyword"} else {"symbol"});
Token::LexError(LERR::ImproperSymbol(s.to_string(), msg).into()) Token::LexError(LERR::ImproperSymbol(s.to_string(), msg).into())
}, },
@ -2481,13 +2202,13 @@ impl<'a> Iterator for TokenIterator<'a> {
}, pos), }, pos),
// Custom keyword // Custom keyword
#[cfg(not(feature = "no_custom_syntax"))] #[cfg(not(feature = "no_custom_syntax"))]
Some((Token::Identifier(s), pos)) if self.engine.custom_keywords.as_ref().map_or(false,|m| m.contains_key(&*s)) => { Some((Token::Identifier(s), pos)) if self.engine.custom_keywords.as_deref().map_or(false,|m| m.contains_key(&*s)) => {
(Token::Custom(s), pos) (Token::Custom(s), pos)
} }
// Custom keyword/symbol - must be disabled // Custom keyword/symbol - must be disabled
#[cfg(not(feature = "no_custom_syntax"))] #[cfg(not(feature = "no_custom_syntax"))]
Some((token, pos)) if token.is_literal() && self.engine.custom_keywords.as_ref().map_or(false,|m| m.contains_key(token.literal_syntax())) => { Some((token, pos)) if token.is_literal() && self.engine.custom_keywords.as_deref().map_or(false,|m| m.contains_key(token.literal_syntax())) => {
if self.engine.disabled_symbols.as_ref().map_or(false,|m| m.contains(token.literal_syntax())) { if self.engine.disabled_symbols.as_deref().map_or(false,|m| m.contains(token.literal_syntax())) {
// Disabled standard keyword/symbol // Disabled standard keyword/symbol
(Token::Custom(Box::new(token.literal_syntax().into())), pos) (Token::Custom(Box::new(token.literal_syntax().into())), pos)
} else { } else {
@ -2496,7 +2217,7 @@ impl<'a> Iterator for TokenIterator<'a> {
} }
} }
// Disabled symbol // Disabled symbol
Some((token, pos)) if token.is_literal() && self.engine.disabled_symbols.as_ref().map_or(false,|m| m.contains(token.literal_syntax())) => { Some((token, pos)) if token.is_literal() && self.engine.disabled_symbols.as_deref().map_or(false,|m| m.contains(token.literal_syntax())) => {
(Token::Reserved(Box::new(token.literal_syntax().into())), pos) (Token::Reserved(Box::new(token.literal_syntax().into())), pos)
} }
// Normal symbol // Normal symbol
@ -2554,10 +2275,7 @@ impl Engine {
TokenIterator { TokenIterator {
engine: self, engine: self,
state: TokenizeState { state: TokenizeState {
#[cfg(not(feature = "unchecked"))] max_string_len: NonZeroUsize::new(self.max_string_size()),
max_string_size: self.limits.max_string_size,
#[cfg(feature = "unchecked")]
max_string_size: None,
next_token_cannot_be_unary: false, next_token_cannot_be_unary: false,
tokenizer_control: buffer, tokenizer_control: buffer,
comment_level: 0, comment_level: 0,

View File

@ -1,7 +1,7 @@
//! Collection of custom types. //! Collection of custom types.
use crate::Identifier; use crate::Identifier;
use std::{any::type_name, collections::BTreeMap, fmt}; use std::{any::type_name, collections::BTreeMap};
/// _(internals)_ Information for a custom type. /// _(internals)_ Information for a custom type.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
@ -13,18 +13,9 @@ pub struct CustomTypeInfo {
/// _(internals)_ A collection of custom types. /// _(internals)_ A collection of custom types.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[derive(Clone, Hash)] #[derive(Debug, Clone, Hash)]
pub struct CustomTypesCollection(BTreeMap<Identifier, CustomTypeInfo>); pub struct CustomTypesCollection(BTreeMap<Identifier, CustomTypeInfo>);
impl fmt::Debug for CustomTypesCollection {
#[cold]
#[inline(never)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("CustomTypesCollection ")?;
f.debug_map().entries(self.0.iter()).finish()
}
}
impl Default for CustomTypesCollection { impl Default for CustomTypesCollection {
#[inline(always)] #[inline(always)]
fn default() -> Self { fn default() -> Self {
@ -65,6 +56,7 @@ impl CustomTypesCollection {
} }
/// Find a custom type. /// Find a custom type.
#[inline(always)] #[inline(always)]
#[must_use]
pub fn get(&self, key: &str) -> Option<&CustomTypeInfo> { pub fn get(&self, key: &str) -> Option<&CustomTypeInfo> {
self.0.get(key) self.0.get(key)
} }

View File

@ -1,6 +1,6 @@
//! Helper module which defines the [`Dynamic`] data type. //! Helper module which defines the [`Dynamic`] data type.
use crate::{reify, ExclusiveRange, FnPtr, ImmutableString, InclusiveRange, INT}; use crate::{ExclusiveRange, FnPtr, ImmutableString, InclusiveRange, INT};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{ use std::{
@ -49,11 +49,13 @@ pub type Tag = i16;
const DEFAULT_TAG_VALUE: Tag = 0; const DEFAULT_TAG_VALUE: Tag = 0;
/// Dynamic type containing any value. /// Dynamic type containing any value.
#[must_use]
pub struct Dynamic(pub(crate) Union); pub struct Dynamic(pub(crate) Union);
/// Internal [`Dynamic`] representation. /// Internal [`Dynamic`] representation.
/// ///
/// Most variants are boxed to reduce the size. /// Most variants are boxed to reduce the size.
#[must_use]
pub enum Union { pub enum Union {
/// An error value which should not exist. /// An error value which should not exist.
Null, Null,
@ -107,10 +109,12 @@ pub enum Union {
/// This type provides transparent interoperability between normal [`Dynamic`] and shared /// This type provides transparent interoperability between normal [`Dynamic`] and shared
/// [`Dynamic`] values. /// [`Dynamic`] values.
#[derive(Debug)] #[derive(Debug)]
#[must_use]
pub struct DynamicReadLock<'d, T: Clone>(DynamicReadLockInner<'d, T>); pub struct DynamicReadLock<'d, T: Clone>(DynamicReadLockInner<'d, T>);
/// Different types of read guards for [`DynamicReadLock`]. /// Different types of read guards for [`DynamicReadLock`].
#[derive(Debug)] #[derive(Debug)]
#[must_use]
enum DynamicReadLockInner<'d, T: Clone> { enum DynamicReadLockInner<'d, T: Clone> {
/// A simple reference to a non-shared value. /// A simple reference to a non-shared value.
Reference(&'d T), Reference(&'d T),
@ -139,10 +143,12 @@ impl<'d, T: Any + Clone> Deref for DynamicReadLock<'d, T> {
/// This type provides transparent interoperability between normal [`Dynamic`] and shared /// This type provides transparent interoperability between normal [`Dynamic`] and shared
/// [`Dynamic`] values. /// [`Dynamic`] values.
#[derive(Debug)] #[derive(Debug)]
#[must_use]
pub struct DynamicWriteLock<'d, T: Clone>(DynamicWriteLockInner<'d, T>); pub struct DynamicWriteLock<'d, T: Clone>(DynamicWriteLockInner<'d, T>);
/// Different types of write guards for [`DynamicReadLock`]. /// Different types of write guards for [`DynamicReadLock`].
#[derive(Debug)] #[derive(Debug)]
#[must_use]
enum DynamicWriteLockInner<'d, T: Clone> { enum DynamicWriteLockInner<'d, T: Clone> {
/// A simple mutable reference to a non-shared value. /// A simple mutable reference to a non-shared value.
Reference(&'d mut T), Reference(&'d mut T),
@ -686,7 +692,6 @@ impl Clone for Dynamic {
impl Default for Dynamic { impl Default for Dynamic {
#[inline(always)] #[inline(always)]
#[must_use]
fn default() -> Self { fn default() -> Self {
Self::UNIT Self::UNIT
} }
@ -852,19 +857,16 @@ impl Dynamic {
/// Create a new [`Dynamic`] from a [`bool`]. /// Create a new [`Dynamic`] from a [`bool`].
#[inline(always)] #[inline(always)]
#[must_use]
pub const fn from_bool(value: bool) -> Self { pub const fn from_bool(value: bool) -> Self {
Self(Union::Bool(value, DEFAULT_TAG_VALUE, ReadWrite)) Self(Union::Bool(value, DEFAULT_TAG_VALUE, ReadWrite))
} }
/// Create a new [`Dynamic`] from an [`INT`]. /// Create a new [`Dynamic`] from an [`INT`].
#[inline(always)] #[inline(always)]
#[must_use]
pub const fn from_int(value: INT) -> Self { pub const fn from_int(value: INT) -> Self {
Self(Union::Int(value, DEFAULT_TAG_VALUE, ReadWrite)) Self(Union::Int(value, DEFAULT_TAG_VALUE, ReadWrite))
} }
/// Create a new [`Dynamic`] from a [`char`]. /// Create a new [`Dynamic`] from a [`char`].
#[inline(always)] #[inline(always)]
#[must_use]
pub const fn from_char(value: char) -> Self { pub const fn from_char(value: char) -> Self {
Self(Union::Char(value, DEFAULT_TAG_VALUE, ReadWrite)) Self(Union::Char(value, DEFAULT_TAG_VALUE, ReadWrite))
} }
@ -873,7 +875,6 @@ impl Dynamic {
/// Not available under `no_float`. /// Not available under `no_float`.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[inline(always)] #[inline(always)]
#[must_use]
pub const fn from_float(value: crate::FLOAT) -> Self { pub const fn from_float(value: crate::FLOAT) -> Self {
Self(Union::Float( Self(Union::Float(
super::FloatWrapper::new(value), super::FloatWrapper::new(value),
@ -886,28 +887,24 @@ impl Dynamic {
/// Exported under the `decimal` feature only. /// Exported under the `decimal` feature only.
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
#[inline(always)] #[inline(always)]
#[must_use]
pub fn from_decimal(value: rust_decimal::Decimal) -> Self { pub fn from_decimal(value: rust_decimal::Decimal) -> Self {
Self(Union::Decimal(value.into(), DEFAULT_TAG_VALUE, ReadWrite)) Self(Union::Decimal(value.into(), DEFAULT_TAG_VALUE, ReadWrite))
} }
/// Create a [`Dynamic`] from an [`Array`][crate::Array]. /// Create a [`Dynamic`] from an [`Array`][crate::Array].
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
#[inline(always)] #[inline(always)]
#[must_use]
pub fn from_array(array: crate::Array) -> Self { pub fn from_array(array: crate::Array) -> Self {
Self(Union::Array(array.into(), DEFAULT_TAG_VALUE, ReadWrite)) Self(Union::Array(array.into(), DEFAULT_TAG_VALUE, ReadWrite))
} }
/// Create a [`Dynamic`] from a [`Blob`][crate::Blob]. /// Create a [`Dynamic`] from a [`Blob`][crate::Blob].
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
#[inline(always)] #[inline(always)]
#[must_use]
pub fn from_blob(blob: crate::Blob) -> Self { pub fn from_blob(blob: crate::Blob) -> Self {
Self(Union::Blob(blob.into(), DEFAULT_TAG_VALUE, ReadWrite)) Self(Union::Blob(blob.into(), DEFAULT_TAG_VALUE, ReadWrite))
} }
/// Create a [`Dynamic`] from a [`Map`][crate::Map]. /// Create a [`Dynamic`] from a [`Map`][crate::Map].
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[inline(always)] #[inline(always)]
#[must_use]
pub fn from_map(map: crate::Map) -> Self { pub fn from_map(map: crate::Map) -> Self {
Self(Union::Map(map.into(), DEFAULT_TAG_VALUE, ReadWrite)) Self(Union::Map(map.into(), DEFAULT_TAG_VALUE, ReadWrite))
} }
@ -916,7 +913,6 @@ impl Dynamic {
/// Not available under `no-std` or `no_time`. /// Not available under `no-std` or `no_time`.
#[cfg(not(feature = "no_time"))] #[cfg(not(feature = "no_time"))]
#[inline(always)] #[inline(always)]
#[must_use]
pub fn from_timestamp(value: Instant) -> Self { pub fn from_timestamp(value: Instant) -> Self {
Self(Union::TimeStamp(value.into(), DEFAULT_TAG_VALUE, ReadWrite)) Self(Union::TimeStamp(value.into(), DEFAULT_TAG_VALUE, ReadWrite))
} }
@ -991,7 +987,6 @@ impl Dynamic {
} }
/// Make this [`Dynamic`] read-only (i.e. a constant). /// Make this [`Dynamic`] read-only (i.e. a constant).
#[inline(always)] #[inline(always)]
#[must_use]
pub fn into_read_only(self) -> Self { pub fn into_read_only(self) -> Self {
let mut value = self; let mut value = self;
value.set_access_mode(AccessMode::ReadOnly); value.set_access_mode(AccessMode::ReadOnly);
@ -1085,7 +1080,6 @@ impl Dynamic {
/// assert_eq!(new_result.to_string(), "hello"); /// assert_eq!(new_result.to_string(), "hello");
/// ``` /// ```
#[inline] #[inline]
#[must_use]
pub fn from<T: Variant + Clone>(value: T) -> Self { pub fn from<T: Variant + Clone>(value: T) -> Self {
// Coded this way in order to maximally leverage potentials for dead-code removal. // Coded this way in order to maximally leverage potentials for dead-code removal.
@ -1143,7 +1137,6 @@ impl Dynamic {
/// If the [`Dynamic`] value is already shared, this method returns itself. /// If the [`Dynamic`] value is already shared, this method returns itself.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[inline] #[inline]
#[must_use]
pub fn into_shared(self) -> Self { pub fn into_shared(self) -> Self {
let _access = self.access_mode(); let _access = self.access_mode();
@ -1182,6 +1175,7 @@ impl Dynamic {
/// ``` /// ```
#[inline] #[inline]
#[must_use] #[must_use]
#[allow(unused_mut)]
pub fn try_cast<T: Any>(mut self) -> Option<T> { pub fn try_cast<T: Any>(mut self) -> Option<T> {
// Coded this way in order to maximally leverage potentials for dead-code removal. // Coded this way in order to maximally leverage potentials for dead-code removal.
@ -1365,7 +1359,6 @@ impl Dynamic {
/// ///
/// If the [`Dynamic`] is a shared value, it returns a cloned copy of the shared value. /// If the [`Dynamic`] is a shared value, it returns a cloned copy of the shared value.
#[inline] #[inline]
#[must_use]
pub fn flatten_clone(&self) -> Self { pub fn flatten_clone(&self) -> Self {
match self.0 { match self.0 {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
@ -1380,7 +1373,6 @@ impl Dynamic {
/// If the [`Dynamic`] is a shared value, it returns the shared value if there are no /// If the [`Dynamic`] is a shared value, it returns the shared value if there are no
/// outstanding references, or a cloned copy. /// outstanding references, or a cloned copy.
#[inline] #[inline]
#[must_use]
pub fn flatten(self) -> Self { pub fn flatten(self) -> Self {
match self.0 { match self.0 {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
@ -1451,7 +1443,6 @@ impl Dynamic {
/// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1). /// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1).
/// Otherwise, this call panics if the data is currently borrowed for write. /// Otherwise, this call panics if the data is currently borrowed for write.
#[inline] #[inline]
#[must_use]
pub fn read_lock<T: Any + Clone>(&self) -> Option<DynamicReadLock<T>> { pub fn read_lock<T: Any + Clone>(&self) -> Option<DynamicReadLock<T>> {
match self.0 { match self.0 {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
@ -1483,7 +1474,6 @@ impl Dynamic {
/// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1). /// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1).
/// Otherwise, this call panics if the data is currently borrowed for write. /// Otherwise, this call panics if the data is currently borrowed for write.
#[inline] #[inline]
#[must_use]
pub fn write_lock<T: Any + Clone>(&mut self) -> Option<DynamicWriteLock<T>> { pub fn write_lock<T: Any + Clone>(&mut self) -> Option<DynamicWriteLock<T>> {
match self.0 { match self.0 {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]

View File

@ -135,22 +135,20 @@ impl fmt::Display for EvalAltResult {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Self::ErrorInFunctionCall(s, src, err, ..) if crate::parser::is_anonymous_fn(s) => { Self::ErrorInFunctionCall(s, src, err, ..) if crate::parser::is_anonymous_fn(s) => {
write!(f, "{err} in call to closure")?; write!(f, "{err}\nin closure call")?;
if !src.is_empty() { if !src.is_empty() {
write!(f, " @ '{src}'")?; write!(f, " @ '{src}'")?;
} }
} }
Self::ErrorInFunctionCall(s, src, err, ..) => { Self::ErrorInFunctionCall(s, src, err, ..) => {
write!(f, "{err} in call to function {s}")?; write!(f, "{err}\nin call to function '{s}'")?;
if !src.is_empty() { if !src.is_empty() {
write!(f, " @ '{src}'")?; write!(f, " @ '{src}'")?;
} }
} }
Self::ErrorInModule(s, err, ..) if s.is_empty() => { Self::ErrorInModule(s, err, ..) if s.is_empty() => write!(f, "{err}\nin module")?,
write!(f, "Error in module > {err}")? Self::ErrorInModule(s, err, ..) => write!(f, "{err}\nin module '{s}'")?,
}
Self::ErrorInModule(s, err, ..) => write!(f, "Error in module '{s}' > {err}")?,
Self::ErrorVariableExists(s, ..) => write!(f, "Variable already defined: {s}")?, Self::ErrorVariableExists(s, ..) => write!(f, "Variable already defined: {s}")?,
Self::ErrorForbiddenVariable(s, ..) => write!(f, "Forbidden variable name: {s}")?, Self::ErrorForbiddenVariable(s, ..) => write!(f, "Forbidden variable name: {s}")?,
@ -159,16 +157,14 @@ impl fmt::Display for EvalAltResult {
Self::ErrorIndexNotFound(s, ..) => write!(f, "Invalid index: {s}")?, Self::ErrorIndexNotFound(s, ..) => write!(f, "Invalid index: {s}")?,
Self::ErrorFunctionNotFound(s, ..) => write!(f, "Function not found: {s}")?, Self::ErrorFunctionNotFound(s, ..) => write!(f, "Function not found: {s}")?,
Self::ErrorModuleNotFound(s, ..) => write!(f, "Module not found: {s}")?, Self::ErrorModuleNotFound(s, ..) => write!(f, "Module not found: {s}")?,
Self::ErrorDataRace(s, ..) => { Self::ErrorDataRace(s, ..) => write!(f, "Data race detected on variable '{s}'")?,
write!(f, "Data race detected when accessing variable: {s}")?
}
Self::ErrorDotExpr(s, ..) if s.is_empty() => f.write_str("Malformed dot expression")?, Self::ErrorDotExpr(s, ..) if s.is_empty() => f.write_str("Malformed dot expression")?,
Self::ErrorDotExpr(s, ..) => f.write_str(s)?, Self::ErrorDotExpr(s, ..) => f.write_str(s)?,
Self::ErrorIndexingType(s, ..) => write!(f, "Indexer unavailable: {s}")?, Self::ErrorIndexingType(s, ..) => write!(f, "Indexer unavailable: {s}")?,
Self::ErrorUnboundThis(..) => f.write_str("'this' not bound")?, Self::ErrorUnboundThis(..) => f.write_str("'this' not bound")?,
Self::ErrorFor(..) => f.write_str("For loop expects an iterable type")?, Self::ErrorFor(..) => f.write_str("For loop expects iterable type")?,
Self::ErrorTooManyOperations(..) => f.write_str("Too many operations")?, Self::ErrorTooManyOperations(..) => f.write_str("Too many operations")?,
Self::ErrorTooManyModules(..) => f.write_str("Too many modules imported")?, Self::ErrorTooManyModules(..) => f.write_str("Too many modules imported")?,
Self::ErrorStackOverflow(..) => f.write_str("Stack overflow")?, Self::ErrorStackOverflow(..) => f.write_str("Stack overflow")?,
@ -188,31 +184,25 @@ impl fmt::Display for EvalAltResult {
if s.starts_with(crate::engine::FN_GET) => if s.starts_with(crate::engine::FN_GET) =>
{ {
let prop = &s[crate::engine::FN_GET.len()..]; let prop = &s[crate::engine::FN_GET.len()..];
write!( write!(f, "Non-pure property {prop} cannot be accessed on constant")?
f,
"Property {prop} is not pure and cannot be accessed on a constant"
)?
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Self::ErrorNonPureMethodCallOnConstant(s, ..) Self::ErrorNonPureMethodCallOnConstant(s, ..)
if s.starts_with(crate::engine::FN_SET) => if s.starts_with(crate::engine::FN_SET) =>
{ {
let prop = &s[crate::engine::FN_SET.len()..]; let prop = &s[crate::engine::FN_SET.len()..];
write!(f, "Cannot modify property '{prop}' of a constant")? write!(f, "Cannot modify property '{prop}' of constant")?
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Self::ErrorNonPureMethodCallOnConstant(s, ..) if s == crate::engine::FN_IDX_GET => { Self::ErrorNonPureMethodCallOnConstant(s, ..) if s == crate::engine::FN_IDX_GET => {
write!( write!(f, "Non-pure indexer cannot be accessed on constant")?
f,
"Indexer is not pure and cannot be accessed on a constant"
)?
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Self::ErrorNonPureMethodCallOnConstant(s, ..) if s == crate::engine::FN_IDX_SET => { Self::ErrorNonPureMethodCallOnConstant(s, ..) if s == crate::engine::FN_IDX_SET => {
write!(f, "Cannot assign to the indexer of a constant")? write!(f, "Cannot assign to indexer of constant")?
} }
Self::ErrorNonPureMethodCallOnConstant(s, ..) => { Self::ErrorNonPureMethodCallOnConstant(s, ..) => {
write!(f, "Non-pure method '{s}' cannot be called on a constant")? write!(f, "Non-pure method '{s}' cannot be called on constant")?
} }
Self::ErrorAssignmentToConstant(s, ..) => write!(f, "Cannot modify constant {s}")?, Self::ErrorAssignmentToConstant(s, ..) => write!(f, "Cannot modify constant {s}")?,
@ -230,8 +220,8 @@ impl fmt::Display for EvalAltResult {
Self::ErrorArithmetic(s, ..) if s.is_empty() => f.write_str("Arithmetic error")?, Self::ErrorArithmetic(s, ..) if s.is_empty() => f.write_str("Arithmetic error")?,
Self::ErrorArithmetic(s, ..) => f.write_str(s)?, Self::ErrorArithmetic(s, ..) => f.write_str(s)?,
Self::LoopBreak(true, ..) => f.write_str("'break' must be inside a loop")?, Self::LoopBreak(true, ..) => f.write_str("'break' must be within a loop")?,
Self::LoopBreak(false, ..) => f.write_str("'continue' must be inside a loop")?, Self::LoopBreak(false, ..) => f.write_str("'continue' must be within a loop")?,
Self::Return(..) => f.write_str("NOT AN ERROR - function returns value")?, Self::Return(..) => f.write_str("NOT AN ERROR - function returns value")?,
@ -261,7 +251,7 @@ impl fmt::Display for EvalAltResult {
f, f,
"Bit-field index {index} out of bounds: only {max} bits in bit-field", "Bit-field index {index} out of bounds: only {max} bits in bit-field",
)?, )?,
Self::ErrorDataTooLarge(typ, ..) => write!(f, "{typ} exceeds maximum limit")?, Self::ErrorDataTooLarge(typ, ..) => write!(f, "{typ} too large")?,
Self::ErrorCustomSyntax(s, tokens, ..) => write!(f, "{s}: {}", tokens.join(" "))?, Self::ErrorCustomSyntax(s, tokens, ..) => write!(f, "{s}: {}", tokens.join(" "))?,
} }

View File

@ -15,6 +15,7 @@ use num_traits::float::FloatCore as Float;
/// ///
/// Not available under `no_float`. /// Not available under `no_float`.
#[derive(Clone, Copy, Eq, PartialEq, PartialOrd)] #[derive(Clone, Copy, Eq, PartialEq, PartialOrd)]
#[must_use]
pub struct FloatWrapper<F>(F); pub struct FloatWrapper<F>(F);
impl Hash for FloatWrapper<crate::FLOAT> { impl Hash for FloatWrapper<crate::FLOAT> {
@ -108,7 +109,6 @@ impl<F: Float> FloatWrapper<F> {
/// Create a new [`FloatWrapper`]. /// Create a new [`FloatWrapper`].
#[inline(always)] #[inline(always)]
#[must_use]
pub const fn new(value: F) -> Self { pub const fn new(value: F) -> Self {
Self(value) Self(value)
} }

View File

@ -4,8 +4,8 @@ use crate::eval::GlobalRuntimeState;
use crate::tokenizer::is_valid_function_name; use crate::tokenizer::is_valid_function_name;
use crate::types::dynamic::Variant; use crate::types::dynamic::Variant;
use crate::{ use crate::{
reify, Dynamic, Engine, FuncArgs, ImmutableString, NativeCallContext, Position, RhaiError, Dynamic, Engine, FuncArgs, ImmutableString, NativeCallContext, Position, RhaiError, RhaiResult,
RhaiResult, RhaiResultOf, StaticVec, AST, ERR, RhaiResultOf, StaticVec, AST, ERR,
}; };
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;

View File

@ -24,7 +24,6 @@ pub const MAX_STRING_LEN: usize = 24;
/// _(internals)_ A cache for interned strings. /// _(internals)_ A cache for interned strings.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[derive(Clone)] #[derive(Clone)]
#[must_use]
pub struct StringsInterner { pub struct StringsInterner {
/// Cached strings. /// Cached strings.
cache: StraightHashMap<ImmutableString>, cache: StraightHashMap<ImmutableString>,
@ -34,6 +33,7 @@ pub struct StringsInterner {
impl Default for StringsInterner { impl Default for StringsInterner {
#[inline(always)] #[inline(always)]
#[must_use]
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
} }
@ -50,6 +50,7 @@ impl fmt::Debug for StringsInterner {
impl StringsInterner { impl StringsInterner {
/// Create a new [`StringsInterner`]. /// Create a new [`StringsInterner`].
#[inline(always)] #[inline(always)]
#[must_use]
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
cache: StraightHashMap::default(), cache: StraightHashMap::default(),

View File

@ -1,5 +1,8 @@
//! Module defining Rhai data types. //! Module defining Rhai data types.
#[macro_use]
pub mod restore;
pub mod bloom_filter; pub mod bloom_filter;
pub mod custom_types; pub mod custom_types;
pub mod dynamic; pub mod dynamic;
@ -9,7 +12,8 @@ pub mod fn_ptr;
pub mod immutable_string; pub mod immutable_string;
pub mod interner; pub mod interner;
pub mod parse_error; pub mod parse_error;
pub mod restore; pub mod position;
pub mod position_none;
pub mod scope; pub mod scope;
pub mod variant; pub mod variant;
@ -25,6 +29,12 @@ pub use fn_ptr::FnPtr;
pub use immutable_string::ImmutableString; pub use immutable_string::ImmutableString;
pub use interner::StringsInterner; pub use interner::StringsInterner;
pub use parse_error::{LexError, ParseError, ParseErrorType}; pub use parse_error::{LexError, ParseError, ParseErrorType};
#[cfg(not(feature = "no_position"))]
pub use position::{Position, Span};
#[cfg(feature = "no_position")]
pub use position_none::{Position, Span};
pub use restore::RestoreOnDrop; pub use restore::RestoreOnDrop;
pub use scope::Scope; pub use scope::Scope;
pub use variant::Variant; pub use variant::Variant;

View File

@ -19,7 +19,7 @@ pub enum LexError {
UnexpectedInput(String), UnexpectedInput(String),
/// A string literal is not terminated before a new-line or EOF. /// A string literal is not terminated before a new-line or EOF.
UnterminatedString, UnterminatedString,
/// An identifier is in an invalid format. /// An identifier or string literal is longer than the maximum allowed length.
StringTooLong(usize), StringTooLong(usize),
/// An string/character/numeric escape sequence is in an invalid format. /// An string/character/numeric escape sequence is in an invalid format.
MalformedEscapeSequence(String), MalformedEscapeSequence(String),
@ -44,11 +44,7 @@ impl fmt::Display for LexError {
Self::MalformedChar(s) => write!(f, "Invalid character: '{s}'"), Self::MalformedChar(s) => write!(f, "Invalid character: '{s}'"),
Self::MalformedIdentifier(s) => write!(f, "Variable name is not proper: '{s}'"), Self::MalformedIdentifier(s) => write!(f, "Variable name is not proper: '{s}'"),
Self::UnterminatedString => f.write_str("Open string is not terminated"), Self::UnterminatedString => f.write_str("Open string is not terminated"),
Self::StringTooLong(max) => write!( Self::StringTooLong(max) => write!(f, "String is too long (max {max})"),
f,
"Length of string literal exceeds the maximum limit ({})",
max
),
Self::ImproperSymbol(s, d) if d.is_empty() => { Self::ImproperSymbol(s, d) if d.is_empty() => {
write!(f, "Invalid symbol encountered: '{s}'") write!(f, "Invalid symbol encountered: '{s}'")
} }
@ -262,7 +258,7 @@ impl From<LexError> for ParseErrorType {
fn from(err: LexError) -> Self { fn from(err: LexError) -> Self {
match err { match err {
LexError::StringTooLong(max) => { LexError::StringTooLong(max) => {
Self::LiteralTooLarge("Length of string literal".to_string(), max) Self::LiteralTooLarge("Length of string".to_string(), max)
} }
_ => Self::BadInput(err), _ => Self::BadInput(err),
} }

286
src/types/position.rs Normal file
View File

@ -0,0 +1,286 @@
//! Script character position type.
#![cfg(not(feature = "no_position"))]
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{
fmt,
ops::{Add, AddAssign},
};
/// A location (line number + character position) in the input script.
///
/// # Limitations
///
/// In order to keep footprint small, both line number and character position have 16-bit resolution,
/// meaning they go up to a maximum of 65,535 lines and 65,535 characters per line.
///
/// Advancing beyond the maximum line length or maximum number of lines is not an error but has no effect.
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
pub struct Position {
/// Line number: 0 = none
line: u16,
/// Character position: 0 = BOL
pos: u16,
}
impl Position {
/// A [`Position`] representing no position.
pub const NONE: Self = Self { line: 0, pos: 0 };
/// A [`Position`] representing the first position.
pub const START: Self = Self { line: 1, pos: 0 };
/// Create a new [`Position`].
///
/// `line` must not be zero.
///
/// If `position` is zero, then it is at the beginning of a line.
///
/// # Panics
///
/// Panics if `line` is zero.
#[inline]
#[must_use]
pub const fn new(line: u16, position: u16) -> Self {
assert!(line != 0, "line cannot be zero");
let _pos = position;
Self { line, pos: _pos }
}
/// Get the line number (1-based), or [`None`] if there is no position.
///
/// Always returns [`None`] under `no_position`.
#[inline]
#[must_use]
pub const fn line(self) -> Option<usize> {
if self.is_none() {
None
} else {
Some(self.line as usize)
}
}
/// Get the character position (1-based), or [`None`] if at beginning of a line.
///
/// Always returns [`None`] under `no_position`.
#[inline]
#[must_use]
pub const fn position(self) -> Option<usize> {
if self.is_none() || self.pos == 0 {
None
} else {
Some(self.pos as usize)
}
}
/// Advance by one character position.
#[inline]
pub(crate) fn advance(&mut self) {
assert!(!self.is_none(), "cannot advance Position::none");
// Advance up to maximum position
if self.pos < u16::MAX {
self.pos += 1;
}
}
/// Go backwards by one character position.
///
/// # Panics
///
/// Panics if already at beginning of a line - cannot rewind to a previous line.
#[inline]
pub(crate) fn rewind(&mut self) {
assert!(!self.is_none(), "cannot rewind Position::none");
assert!(self.pos > 0, "cannot rewind at position 0");
self.pos -= 1;
}
/// Advance to the next line.
#[inline]
pub(crate) fn new_line(&mut self) {
assert!(!self.is_none(), "cannot advance Position::none");
// Advance up to maximum position
if self.line < u16::MAX {
self.line += 1;
self.pos = 0;
}
}
/// Is this [`Position`] at the beginning of a line?
///
/// Always returns `false` under `no_position`.
#[inline]
#[must_use]
pub const fn is_beginning_of_line(self) -> bool {
self.pos == 0 && !self.is_none()
}
/// Is there no [`Position`]?
///
/// Always returns `true` under `no_position`.
#[inline]
#[must_use]
pub const fn is_none(self) -> bool {
self.line == 0 && self.pos == 0
}
/// Returns an fallback [`Position`] if it is [`NONE`][Position::NONE]?
///
/// Always returns the fallback under `no_position`.
#[inline]
#[must_use]
pub const fn or_else(self, pos: Self) -> Self {
if self.is_none() {
pos
} else {
self
}
}
/// Print this [`Position`] for debug purposes.
#[inline]
pub(crate) fn debug_print(self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.is_none() {
write!(_f, " @ {:?}", self)?;
}
Ok(())
}
}
impl Default for Position {
#[inline(always)]
#[must_use]
fn default() -> Self {
Self::START
}
}
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_none() {
write!(f, "none")
} else {
write!(f, "line {}, position {}", self.line, self.pos)
}
}
}
impl fmt::Debug for Position {
#[cold]
#[inline(never)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_none() {
f.write_str("none")
} else {
if self.is_beginning_of_line() {
write!(f, "{}", self.line)
} else {
write!(f, "{}:{}", self.line, self.pos)
}
}
}
}
impl Add for Position {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
if rhs.is_none() {
self
} else {
Self {
line: self.line + rhs.line - 1,
pos: if rhs.is_beginning_of_line() {
self.pos
} else {
self.pos + rhs.pos - 1
},
}
}
}
}
impl AddAssign for Position {
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
}
}
/// _(internals)_ A span consisting of a starting and an ending [positions][Position].
/// Exported under the `internals` feature only.
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
pub struct Span {
/// Starting [position][Position].
start: Position,
/// Ending [position][Position].
end: Position,
}
impl Default for Span {
#[inline(always)]
#[must_use]
fn default() -> Self {
Self::NONE
}
}
impl Span {
/// Empty [`Span`].
pub const NONE: Self = Self::new(Position::NONE, Position::NONE);
/// Create a new [`Span`].
#[inline(always)]
#[must_use]
pub const fn new(start: Position, end: Position) -> Self {
Self { start, end }
}
/// Is this [`Span`] non-existent?
///
/// Always returns `true` under `no_position`.
#[inline]
#[must_use]
pub const fn is_none(&self) -> bool {
self.start.is_none() && self.end.is_none()
}
/// Get the [`Span`]'s starting [position][Position].
///
/// Always returns [`Position::NONE`] under `no_position`.
#[inline(always)]
#[must_use]
pub const fn start(&self) -> Position {
self.start
}
/// Get the [`Span`]'s ending [position][Position].
///
/// Always returns [`Position::NONE`] under `no_position`.
#[inline(always)]
#[must_use]
pub const fn end(&self) -> Position {
self.end
}
}
impl fmt::Display for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let _f = f;
match (self.start(), self.end()) {
(Position::NONE, Position::NONE) => write!(_f, "{:?}", Position::NONE),
(Position::NONE, end) => write!(_f, "..{:?}", end),
(start, Position::NONE) => write!(_f, "{:?}", start),
(start, end) if start.line() != end.line() => {
write!(_f, "{:?}-{:?}", start, end)
}
(start, end) => write!(
_f,
"{}:{}-{}",
start.line().unwrap(),
start.position().unwrap_or(0),
end.position().unwrap_or(0)
),
}
}
}
impl fmt::Debug for Span {
#[cold]
#[inline(never)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}

182
src/types/position_none.rs Normal file
View File

@ -0,0 +1,182 @@
//! Placeholder script character position type.
#![cfg(feature = "no_position")]
#![allow(unused_variables)]
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{
fmt,
ops::{Add, AddAssign},
};
/// A location (line number + character position) in the input script.
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
pub struct Position;
impl Position {
/// A [`Position`] representing no position.
pub const NONE: Self = Self;
/// A [`Position`] representing the first position.
pub const START: Self = Self;
/// Create a new [`Position`].
#[inline(always)]
#[must_use]
pub const fn new(line: u16, position: u16) -> Self {
Self
}
/// Get the line number (1-based), or [`None`] if there is no position.
///
/// Always returns [`None`].
#[inline(always)]
#[must_use]
pub const fn line(self) -> Option<usize> {
None
}
/// Get the character position (1-based), or [`None`] if at beginning of a line.
///
/// Always returns [`None`].
#[inline(always)]
#[must_use]
pub const fn position(self) -> Option<usize> {
None
}
/// Advance by one character position.
#[inline(always)]
pub(crate) fn advance(&mut self) {}
/// Go backwards by one character position.
#[inline(always)]
pub(crate) fn rewind(&mut self) {}
/// Advance to the next line.
#[inline(always)]
pub(crate) fn new_line(&mut self) {}
/// Is this [`Position`] at the beginning of a line?
///
/// Always returns `false`.
#[inline(always)]
#[must_use]
pub const fn is_beginning_of_line(self) -> bool {
false
}
/// Is there no [`Position`]?
///
/// Always returns `true`.
#[inline(always)]
#[must_use]
pub const fn is_none(self) -> bool {
true
}
/// Returns an fallback [`Position`] if it is [`NONE`][Position::NONE]?
///
/// Always returns the fallback.
#[inline(always)]
#[must_use]
pub const fn or_else(self, pos: Self) -> Self {
pos
}
/// Print this [`Position`] for debug purposes.
#[inline(always)]
pub(crate) fn debug_print(self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
Ok(())
}
}
impl Default for Position {
#[inline(always)]
#[must_use]
fn default() -> Self {
Self
}
}
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "none")
}
}
impl fmt::Debug for Position {
#[cold]
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("none")
}
}
impl Add for Position {
type Output = Self;
#[inline(always)]
fn add(self, rhs: Self) -> Self::Output {
Self
}
}
impl AddAssign for Position {
#[inline(always)]
fn add_assign(&mut self, rhs: Self) {}
}
/// _(internals)_ A span consisting of a starting and an ending [positions][Position].
/// Exported under the `internals` feature only.
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
pub struct Span;
impl Default for Span {
#[inline(always)]
#[must_use]
fn default() -> Self {
Self
}
}
impl Span {
/// Empty [`Span`].
pub const NONE: Self = Self;
/// Create a new [`Span`].
#[inline(always)]
#[must_use]
pub const fn new(start: Position, end: Position) -> Self {
Self
}
/// Is this [`Span`] non-existent?
///
/// Always returns `true`.
#[inline(always)]
#[must_use]
pub const fn is_none(&self) -> bool {
true
}
/// Get the [`Span`]'s starting [position][Position].
///
/// Always returns [`Position::NONE`].
#[inline(always)]
#[must_use]
pub const fn start(&self) -> Position {
Position::NONE
}
/// Get the [`Span`]'s ending [position][Position].
///
/// Always returns [`Position::NONE`].
#[inline(always)]
#[must_use]
pub const fn end(&self) -> Position {
Position::NONE
}
}
impl fmt::Display for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let f = f;
write!(f, "{:?}", Position)
}
}
impl fmt::Debug for Span {
#[cold]
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}

View File

@ -4,6 +4,52 @@ use std::ops::{Deref, DerefMut};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
/// Automatically restore state at the end of the scope.
macro_rules! auto_restore {
(let $temp:ident = $var:ident . $prop:ident; $code:stmt) => {
auto_restore!(let $temp = $var.$prop; $code => move |v| v.$prop = $temp);
};
(let $temp:ident = $var:ident . $prop:ident; $code:stmt => $restore:expr) => {
let $temp = $var.$prop;
$code
auto_restore!($var => $restore);
};
($var:ident => $restore:ident; let $temp:ident = $save:expr;) => {
auto_restore!($var => $restore; let $temp = $save; {});
};
($var:ident if $guard:expr => $restore:ident; let $temp:ident = $save:expr;) => {
auto_restore!($var if $guard => $restore; let $temp = $save; {});
};
($var:ident => $restore:ident; let $temp:ident = $save:expr; $code:stmt) => {
let $temp = $save;
$code
auto_restore!($var => move |v| { v.$restore($temp); });
};
($var:ident if $guard:expr => $restore:ident; let $temp:ident = $save:expr; $code:stmt) => {
let $temp = $save;
$code
auto_restore!($var if $guard => move |v| { v.$restore($temp); });
};
($var:ident => $restore:expr) => {
auto_restore!($var = $var => $restore);
};
($var:ident = $value:expr => $restore:expr) => {
let $var = &mut *crate::types::RestoreOnDrop::lock($value, $restore);
};
($var:ident if $guard:expr => $restore:expr) => {
auto_restore!($var = ($var) if $guard => $restore);
};
($var:ident = ( $value:expr ) if $guard:expr => $restore:expr) => {
let mut __rx__;
let $var = if $guard {
__rx__ = crate::types::RestoreOnDrop::lock($value, $restore);
&mut *__rx__
} else {
&mut *$value
};
};
}
/// Run custom restoration logic upon the end of scope. /// Run custom restoration logic upon the end of scope.
#[must_use] #[must_use]
pub struct RestoreOnDrop<'a, T: ?Sized, R: FnOnce(&mut T)> { pub struct RestoreOnDrop<'a, T: ?Sized, R: FnOnce(&mut T)> {
@ -12,19 +58,6 @@ pub struct RestoreOnDrop<'a, T: ?Sized, R: FnOnce(&mut T)> {
} }
impl<'a, T: ?Sized, R: FnOnce(&mut T)> RestoreOnDrop<'a, T, R> { impl<'a, T: ?Sized, R: FnOnce(&mut T)> RestoreOnDrop<'a, T, R> {
/// Create a new [`RestoreOnDrop`] that locks a mutable reference and runs restoration logic at
/// the end of scope only when `need_restore` is `true`.
///
/// Beware that the end of scope means the end of its lifetime, not necessarily waiting until
/// the current block scope is exited.
#[inline(always)]
pub fn lock_if(need_restore: bool, value: &'a mut T, restore: R) -> Self {
Self {
value,
restore: if need_restore { Some(restore) } else { None },
}
}
/// Create a new [`RestoreOnDrop`] that locks a mutable reference and runs restoration logic at /// Create a new [`RestoreOnDrop`] that locks a mutable reference and runs restoration logic at
/// the end of scope. /// the end of scope.
/// ///
@ -42,9 +75,7 @@ impl<'a, T: ?Sized, R: FnOnce(&mut T)> RestoreOnDrop<'a, T, R> {
impl<'a, T: ?Sized, R: FnOnce(&mut T)> Drop for RestoreOnDrop<'a, T, R> { impl<'a, T: ?Sized, R: FnOnce(&mut T)> Drop for RestoreOnDrop<'a, T, R> {
#[inline(always)] #[inline(always)]
fn drop(&mut self) { fn drop(&mut self) {
if let Some(restore) = self.restore.take() { self.restore.take().unwrap()(self.value);
restore(self.value);
}
} }
} }

View File

@ -386,7 +386,7 @@ impl Scope<'_> {
#[inline(always)] #[inline(always)]
pub fn pop(&mut self) -> &mut Self { pub fn pop(&mut self) -> &mut Self {
self.names.pop().expect("`Scope` must not be empty"); self.names.pop().expect("`Scope` must not be empty");
self.values.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.aliases.pop().expect("`Scope` must not be empty");
self self
} }

36444
t.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@ -21,6 +21,7 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y[-1]")?, 3); assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y[-1]")?, 3);
assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y[-3]")?, 1); assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y[-3]")?, 1);
assert!(engine.eval::<bool>("let y = [1, 2, 3]; 2 in y")?); assert!(engine.eval::<bool>("let y = [1, 2, 3]; 2 in y")?);
assert!(engine.eval::<bool>("let y = [1, 2, 3]; 42 !in y")?);
assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y += 4; y[3]")?, 4); assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y += 4; y[3]")?, 4);
assert_eq!( assert_eq!(
engine.eval::<INT>("let y = [1, 2, 3]; pad(y, 5, 42); len(y)")?, engine.eval::<INT>("let y = [1, 2, 3]; pad(y, 5, 42); len(y)")?,

View File

@ -352,7 +352,7 @@ fn test_call_fn_events() -> Result<(), Box<EvalAltResult>> {
let mut handler = Handler::new(); let mut handler = Handler::new();
assert!(!handler.scope.get_value::<bool>("state").unwrap()); assert!(!handler.scope.get_value::<bool>("state").unwrap());
handler.on_event("update", 999); let _ = handler.on_event("update", 999);
assert!(handler.scope.get_value::<bool>("state").unwrap()); assert!(handler.scope.get_value::<bool>("state").unwrap());
assert_eq!(handler.on_event("start", 999).as_int().unwrap(), 1041); assert_eq!(handler.on_event("start", 999).as_int().unwrap(), 1041);

View File

@ -75,9 +75,15 @@ fn test_comments_doc() -> Result<(), Box<EvalAltResult>> {
", ",
)?; )?;
#[cfg(not(feature = "no_position"))]
assert_eq!( assert_eq!(
ast.iter_functions().next().unwrap().comments[0], ast.iter_functions().next().unwrap().comments[0],
"/** Hello world\n ** how are you?\n **/" "/** Hello world\n** how are you?\n**/"
);
#[cfg(feature = "no_position")]
assert_eq!(
ast.iter_functions().next().unwrap().comments[0],
"/** Hello world\n ** how are you?\n **/",
); );
assert!(engine assert!(engine

View File

@ -17,7 +17,7 @@ fn test_max_string_size() -> Result<(), Box<EvalAltResult>> {
.compile(r#"let x = "hello, world!";"#) .compile(r#"let x = "hello, world!";"#)
.expect_err("should error") .expect_err("should error")
.err_type(), .err_type(),
ParseErrorType::LiteralTooLarge("Length of string literal".to_string(), 10) ParseErrorType::LiteralTooLarge("Length of string".to_string(), 10)
); );
assert_eq!( assert_eq!(
@ -25,7 +25,7 @@ fn test_max_string_size() -> Result<(), Box<EvalAltResult>> {
.compile(r#"let x = "朝に紅顔、暮に白骨";"#) .compile(r#"let x = "朝に紅顔、暮に白骨";"#)
.expect_err("should error") .expect_err("should error")
.err_type(), .err_type(),
ParseErrorType::LiteralTooLarge("Length of string literal".to_string(), 10) ParseErrorType::LiteralTooLarge("Length of string".to_string(), 10)
); );
assert!(matches!( assert!(matches!(

View File

@ -12,7 +12,7 @@ fn test_debugging() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.register_debugger( engine.register_debugger(
|_| Dynamic::UNIT, |_, dbg| dbg,
|_, _, _, _, _| Ok(rhai::debugger::DebuggerCommand::Continue), |_, _, _, _, _| Ok(rhai::debugger::DebuggerCommand::Continue),
); );
@ -47,19 +47,20 @@ fn test_debugger_state() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.register_debugger( engine.register_debugger(
|_| { |_, mut debugger| {
// Say, use an object map for the debugger state // Say, use an object map for the debugger state
let mut state = Map::new(); let mut state = Map::new();
// Initialize properties // Initialize properties
state.insert("hello".into(), (42 as INT).into()); state.insert("hello".into(), (42 as INT).into());
state.insert("foo".into(), false.into()); state.insert("foo".into(), false.into());
Dynamic::from_map(state) debugger.set_state(state);
debugger
}, },
|mut context, _, _, _, _| { |mut context, _, _, _, _| {
// Print debugger state - which is an object map // Print debugger state - which is an object map
println!( println!(
"Current state = {}", "Current state = {}",
context.global_runtime_state_mut().debugger().state() context.global_runtime_state().debugger().state()
); );
// Modify state // Modify state

View File

@ -34,6 +34,15 @@ fn test_loop() -> Result<(), Box<EvalAltResult>> {
ParseErrorType::LoopBreak ParseErrorType::LoopBreak
); );
#[cfg(not(feature = "no_function"))]
assert_eq!(
*engine
.compile("loop { let f = || { break; } }")
.expect_err("should error")
.err_type(),
ParseErrorType::LoopBreak
);
assert_eq!( assert_eq!(
*engine *engine
.compile("let x = 0; if x > 0 { continue; }") .compile("let x = 0; if x > 0 { continue; }")