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
==============
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.
* 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
------------
### `!in`
* A new operator `!in` is added which maps to `!(... in ...)`.
### `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.
@ -21,11 +44,15 @@ Net features
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`.
* The `TypeBuilder` type and `CustomType` trait are no longer marked as volatile.
* `FuncArgs` is also implemented for arrays.
* `Engine::set_XXX` API can now be chained.
* `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

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 }
bitflags = { 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 }
libm = { version = "0.2", default-features = false, optional = true }
@ -43,7 +43,7 @@ serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
[features]
default = ["std", "ahash/runtime-rng"] # ahash/runtime-rng trumps ahash/compile-time-rng
std = ["ahash/std", "num-traits/std", "smartstring/std"]
unchecked = [] # unchecked arithmetic
unchecked = [] # disable safety checks
sync = [] # restrict to only types that implement Send + Sync
no_position = [] # do not track position in the parser
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.
* 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).
* 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).

View File

@ -1,11 +1,11 @@
[package]
name = "rhai_codegen"
version = "1.4.3"
version = "1.5.0"
edition = "2018"
resolver = "2"
authors = ["jhwgh1968", "Stephen Chung"]
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"]
homepage = "https://rhai.rs/book/plugins/index.html"
repository = "https://github.com/rhaiscript/rhai"
@ -24,5 +24,5 @@ syn = { version = "1.0", features = ["full", "parsing", "printing", "proc-macro"
quote = "1"
[dev-dependencies]
rhai = { path = "..", version = "1.11", features = ["metadata"] }
rhai = { path = "..", version = "1.12", features = ["metadata"] }
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>> {
// Find the #[doc] attribute which will turn be read for function documentation.
let mut comments = Vec::new();
let mut buf = String::new();
for attr in attrs {
if let Some(i) = attr.path.get_ident() {
@ -158,17 +159,28 @@ pub fn doc_attributes(attrs: &[syn::Attribute]) -> syn::Result<Vec<String>> {
if line.contains('\n') {
// Must be a block comment `/** ... */`
if !buf.is_empty() {
comments.push(buf.clone());
buf.clear();
}
line.insert_str(0, "/**");
line.push_str("*/");
comments.push(line);
} else {
// 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)

View File

@ -674,6 +674,7 @@ impl ExportedFn {
let arg_count = self.arg_count();
let is_method_call = self.mutable_receiver();
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_exprs = Vec::new();
@ -689,7 +690,7 @@ impl ExportedFn {
let skip_first_arg;
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
@ -860,13 +861,14 @@ impl ExportedFn {
#(#cfg_attrs)*
impl PluginFunction for #type_name {
#[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)*
#return_expr
}
#[inline(always)] fn is_method_call(&self) -> bool { #is_method_call }
#[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 {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut mod_all: syn::ItemMod = input.parse()?;
let fns: Vec<_>;
let mut consts = Vec::new();
let mut custom_types = Vec::new();
@ -269,11 +270,17 @@ impl Module {
let (.., orig_content) = mod_all.content.take().unwrap();
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 {
// Generate new module items.
//
// This is done before inner module recursive generation, because that is destructive.
let mod_gen = crate::rhai_module::generate_body(
&mod_doc,
&mut fns,
&consts,
&custom_types,

View File

@ -26,6 +26,7 @@ pub struct ExportedType {
}
pub fn generate_body(
doc: &str,
fns: &mut [ExportedFn],
consts: &[ExportedConst],
custom_types: &[ExportedType],
@ -230,7 +231,7 @@ pub fn generate_body(
syn::parse2::<syn::Stmt>(quote! {
#(#cfg_attrs)*
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()
});
@ -246,6 +247,17 @@ pub fn generate_body(
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! {
pub mod generate_info {
#[allow(unused_imports)]
@ -254,6 +266,7 @@ pub fn generate_body(
#[doc(hidden)]
pub fn rhai_module_generate() -> Module {
let mut m = Module::new();
#module_docs
rhai_generate_into_module(&mut m, false);
m.build_index();
m

View File

@ -280,12 +280,13 @@ mod generate_tests {
#[inline(always)] pub fn param_types() -> [TypeId; 0usize] { [] }
}
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()))
}
#[inline(always)] fn is_method_call(&self) -> bool { false }
#[inline(always)] fn is_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { false }
}
#[allow(unused)]
#[doc(hidden)]
@ -318,13 +319,14 @@ mod generate_tests {
}
impl PluginFunction for Token {
#[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>();
Ok(Dynamic::from(do_something(arg0)))
}
#[inline(always)] fn is_method_call(&self) -> bool { false }
#[inline(always)] fn is_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { false }
}
#[allow(unused)]
#[doc(hidden)]
@ -357,13 +359,14 @@ mod generate_tests {
}
impl PluginFunction for Token {
#[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>();
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_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { true }
}
#[allow(unused)]
#[doc(hidden)]
@ -399,12 +402,13 @@ mod generate_tests {
}
impl PluginFunction for Token {
#[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()))
}
#[inline(always)] fn is_method_call(&self) -> bool { false }
#[inline(always)] fn is_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { false }
}
#[allow(unused)]
#[doc(hidden)]
@ -432,13 +436,14 @@ mod generate_tests {
}
impl PluginFunction for TestStruct {
#[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>();
Ok(Dynamic::from(do_something(arg0)))
}
#[inline(always)] fn is_method_call(&self) -> bool { false }
#[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 {
#[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 arg1 = mem::take(args[1usize]).cast::<usize>();
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_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { false }
}
#[allow(unused)]
#[doc(hidden)]
@ -505,7 +511,7 @@ mod generate_tests {
}
impl PluginFunction for Token {
#[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 arg0 = &mut args[0usize].write_lock::<usize>().unwrap();
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_pure(&self) -> bool { false }
#[inline(always)] fn has_context(&self) -> bool { false }
}
#[allow(unused)]
#[doc(hidden)]
@ -546,13 +553,14 @@ mod generate_tests {
}
impl PluginFunction for Token {
#[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();
Ok(Dynamic::from(special_print(&arg0)))
}
#[inline(always)] fn is_method_call(&self) -> bool { false }
#[inline(always)] fn is_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { false }
}
#[allow(unused)]
#[doc(hidden)]

View File

@ -92,10 +92,11 @@ mod module_tests {
.cloned()
.collect::<Vec<_>>(),
vec![
"/// This is a doc-comment.",
"/// Another line.",
"/// block doc-comment ",
"/// Final line.",
"\
/// This is a doc-comment.\n\
/// Another line.\n\
/// block doc-comment \n\
/// Final line.",
"/** doc-comment\n in multiple lines\n */"
]
);
@ -385,12 +386,13 @@ mod generate_tests {
}
impl PluginFunction for get_mystic_number_token {
#[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()))
}
#[inline(always)] fn is_method_call(&self) -> bool { false }
#[inline(always)] fn is_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { false }
}
}
};
@ -402,6 +404,12 @@ mod generate_tests {
#[test]
fn one_factory_fn_with_comments_module() {
let input_tokens: TokenStream = quote! {
/// This is the one_fn module!
/** block doc-comment
* multi-line
*/
/// Another line!
/// Final line!
pub mod one_fn {
/// This is a doc-comment.
/// Another line.
@ -418,6 +426,12 @@ mod generate_tests {
};
let expected_tokens = quote! {
/// This is the one_fn module!
/** block doc-comment
* multi-line
*/
/// Another line!
/// Final line!
pub mod one_fn {
/// This is a doc-comment.
/// Another line.
@ -436,6 +450,7 @@ mod generate_tests {
#[doc(hidden)]
pub fn rhai_module_generate() -> Module {
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);
m.build_index();
m
@ -444,11 +459,8 @@ mod generate_tests {
#[doc(hidden)]
pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) {
m.set_fn_with_comments("get_mystic_number", FnNamespace::Internal, FnAccess::Public,
Some(get_mystic_number_token::PARAM_NAMES), &[], &[
"/// This is a doc-comment.",
"/// Another line.",
"/// block doc-comment ",
"/// Final line.",
Some(get_mystic_number_token::PARAM_NAMES), [], [
"/// This is a doc-comment.\n/// Another line.\n/// block doc-comment \n/// Final line.",
"/** doc-comment\n in multiple lines\n */"
], get_mystic_number_token().into());
if flatten {} else {}
@ -463,12 +475,13 @@ mod generate_tests {
}
impl PluginFunction for get_mystic_number_token {
#[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()))
}
#[inline(always)] fn is_method_call(&self) -> bool { false }
#[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 {
#[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>();
Ok(Dynamic::from(add_one_to(arg0)))
}
#[inline(always)] fn is_method_call(&self) -> bool { false }
#[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 {
#[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>();
Ok(Dynamic::from(add_one_to(arg0)))
}
#[inline(always)] fn is_method_call(&self) -> bool { false }
#[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 {
#[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>();
Ok(Dynamic::from(add_one_to(arg0)))
}
#[inline(always)] fn is_method_call(&self) -> bool { false }
#[inline(always)] fn is_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { false }
}
#[allow(non_camel_case_types)]
@ -670,7 +686,7 @@ mod generate_tests {
}
impl PluginFunction for add_n_to_token {
#[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 arg1 = mem::take(args[1usize]).cast::<INT>();
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_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 {
#[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 arg1 = mem::take(args[1usize]).cast::<INT>();
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_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 {
#[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 arg1 = mem::take(args[1usize]).cast::<INT>();
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_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 {
#[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();
Ok(Dynamic::from(get_mystic_number(arg0)))
}
#[inline(always)] fn is_method_call(&self) -> bool { true }
#[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 {
#[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()))
}
#[inline(always)] fn is_method_call(&self) -> bool { false }
#[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 {
#[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();
Ok(Dynamic::from(print_out_to(&arg0)))
}
#[inline(always)] fn is_method_call(&self) -> bool { false }
#[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 {
#[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();
Ok(Dynamic::from(print_out_to(arg0)))
}
#[inline(always)] fn is_method_call(&self) -> bool { false }
#[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 {
#[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 arg0 = &mut args[0usize].write_lock::<FLOAT>().unwrap();
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_pure(&self) -> bool { true }
#[inline(always)] fn has_context(&self) -> bool { false }
}
}
};
@ -1347,13 +1371,14 @@ mod generate_tests {
}
impl PluginFunction for increment_token {
#[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();
Ok(Dynamic::from(increment(arg0)))
}
#[inline(always)] fn is_method_call(&self) -> bool { true }
#[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 {
#[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();
Ok(Dynamic::from(increment(arg0)))
}
#[inline(always)] fn is_method_call(&self) -> bool { true }
#[inline(always)] fn is_pure(&self) -> bool { false }
#[inline(always)] fn has_context(&self) -> bool { false }
}
}
#[allow(unused_imports)]
@ -1492,13 +1518,14 @@ mod generate_tests {
}
impl PluginFunction for increment_token {
#[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();
Ok(Dynamic::from(increment(arg0)))
}
#[inline(always)] fn is_method_call(&self) -> bool { true }
#[inline(always)] fn is_pure(&self) -> bool { false }
#[inline(always)] fn has_context(&self) -> bool { false }
}
}
#[allow(unused_imports)]
@ -1577,13 +1604,14 @@ mod generate_tests {
}
impl PluginFunction for int_foo_token {
#[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();
Ok(Dynamic::from(int_foo(arg0)))
}
#[inline(always)] fn is_method_call(&self) -> bool { true }
#[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 {
#[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();
Ok(Dynamic::from(int_foo(arg0)))
}
#[inline(always)] fn is_method_call(&self) -> bool { true }
#[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 {
#[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 arg0 = &mut args[0usize].write_lock::<u64>().unwrap();
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_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 {
#[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 arg0 = &mut args[0usize].write_lock::<u64>().unwrap();
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_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 {
#[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 arg0 = &mut args[0usize].write_lock::<MyCollection>().unwrap();
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_pure(&self) -> bool { false }
#[inline(always)] fn has_context(&self) -> bool { false }
}
}
};
@ -1889,7 +1921,7 @@ mod generate_tests {
#[cfg(hello)]
impl PluginFunction for get_by_index_token {
#[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 arg0 = &mut args[0usize].write_lock::<MyCollection>().unwrap();
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_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 {
#[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 arg0 = &mut args[0usize].write_lock::<MyCollection>().unwrap();
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_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 {
#[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 arg2 = mem::take(args[2usize]).cast::<FLOAT>();
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_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 {
#[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 arg2 = mem::take(args[2usize]).cast::<FLOAT>();
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_pure(&self) -> bool { false }
#[inline(always)] fn has_context(&self) -> bool { false }
}
}
};

View File

@ -4,7 +4,7 @@
fn init() {
// Can detect system-provided default states!
// 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;
}
// 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'.
fn init() {
// 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;
}
// Add 'obj_state' as new state variable (overwrites any existing)
@ -37,7 +37,7 @@ fn start(data) {
/// 'end' event handler
fn end(data) {
if !state.bool_state || !("start_mode" in state) {
if !state.bool_state || "start_mode" !in state {
throw "Not yet started!";
}
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 use [`Engine::register_type_with_name`] instead.
#[must_use]
pub struct TypeBuilder<'a, T: Variant + Clone> {
engine: &'a mut Engine,
name: Option<&'static str>,
@ -101,7 +102,7 @@ impl<'a, T: Variant + Clone> TypeBuilder<'a, T> {
Self {
engine,
name: None,
_marker: PhantomData::default(),
_marker: PhantomData,
}
}
}
@ -116,10 +117,10 @@ impl<'a, T: Variant + Clone> TypeBuilder<'a, T> {
/// Register a custom function.
#[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,
name: impl AsRef<str> + Into<Identifier>,
method: impl RegisterNativeFunction<A, R, S>,
method: impl RegisterNativeFunction<A, N, C, R, L>,
) -> &mut Self {
self.engine.register_fn(name, method);
self
@ -148,10 +149,10 @@ impl<'a, T: Variant + Clone> TypeBuilder<'a, T> {
///
/// Not available under `no_object`.
#[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,
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 {
self.engine.register_get(name, get_fn);
self
@ -161,10 +162,10 @@ impl<'a, T: Variant + Clone> TypeBuilder<'a, T> {
///
/// Not available under `no_object`.
#[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,
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 {
self.engine.register_set(name, set_fn);
self
@ -176,11 +177,19 @@ impl<'a, T: Variant + Clone> TypeBuilder<'a, T> {
///
/// Not available under `no_object`.
#[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,
name: impl AsRef<str>,
get_fn: impl RegisterNativeFunction<(Mut<T>,), V, S1> + crate::func::SendSync + 'static,
set_fn: impl RegisterNativeFunction<(Mut<T>, V), (), S2> + 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), 2, C2, (), L2>
+ crate::func::SendSync
+ 'static,
) -> &mut Self {
self.engine.register_get_set(name, get_fn, set_fn);
self
@ -195,9 +204,14 @@ impl<'a, T: Variant + Clone> TypeBuilder<'a, T> {
///
/// Not available under both `no_index` and `no_object`.
#[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,
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 {
self.engine.register_indexer_get(get_fn);
self
@ -207,9 +221,16 @@ impl<'a, T: Variant + Clone> TypeBuilder<'a, T> {
///
/// Not available under both `no_index` and `no_object`.
#[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,
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 {
self.engine.register_indexer_set(set_fn);
self
@ -219,10 +240,19 @@ impl<'a, T: Variant + Clone> TypeBuilder<'a, T> {
///
/// Not available under both `no_index` and `no_object`.
#[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,
get_fn: impl RegisterNativeFunction<(Mut<T>, X), V, S1> + crate::func::SendSync + 'static,
set_fn: impl RegisterNativeFunction<(Mut<T>, X, V), (), S2> + 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), 3, C2, (), L2>
+ crate::func::SendSync
+ 'static,
) -> &mut Self {
self.engine.register_indexer_get_set(get_fn, set_fn);
self

View File

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

View File

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

View File

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

View File

@ -29,6 +29,7 @@ impl Engine {
/// # }
/// ```
#[inline(always)]
#[must_use]
pub fn definitions(&self) -> Definitions {
Definitions {
engine: self,
@ -55,6 +56,7 @@ impl Engine {
/// # }
/// ```
#[inline(always)]
#[must_use]
pub fn definitions_with_scope<'e>(&'e self, scope: &'e Scope<'e>) -> Definitions<'e> {
Definitions {
engine: self,
@ -67,7 +69,6 @@ impl Engine {
/// Internal configuration for module generation.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[non_exhaustive]
#[must_use]
pub struct DefinitionsConfig {
/// Write `module ...` headers in definition files (default `false`).
pub write_headers: bool,
@ -77,6 +78,7 @@ pub struct DefinitionsConfig {
impl Default for DefinitionsConfig {
#[inline(always)]
#[must_use]
fn default() -> Self {
Self {
write_headers: false,
@ -89,7 +91,6 @@ impl Default for DefinitionsConfig {
/// contents of an [`Engine`].
/// Exported under the `internals` and `metadata` feature only.
#[derive(Debug, Clone)]
#[must_use]
pub struct Definitions<'e> {
/// The [`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
/// (i.e. `write_to*` and `*_file` methods).
#[inline(always)]
#[must_use]
pub const fn with_headers(mut self, headers: bool) -> Self {
self.config.write_headers = headers;
self
}
/// Include standard packages when writing definition files.
#[inline(always)]
#[must_use]
pub const fn include_standard_packages(mut self, include_standard_packages: bool) -> Self {
self.config.include_standard_packages = include_standard_packages;
self
@ -128,6 +131,7 @@ impl Definitions<'_> {
}
/// Get the configuration.
#[inline(always)]
#[must_use]
pub(crate) const fn config(&self) -> &DefinitionsConfig {
&self.config
}
@ -368,8 +372,9 @@ impl Definitions<'_> {
let mut m = self
.engine
.global_sub_modules
.iter()
.flat_map(|m| m.iter())
.as_deref()
.into_iter()
.flatten()
.map(move |(name, module)| {
(
name.to_string(),
@ -391,6 +396,7 @@ impl Definitions<'_> {
impl Module {
/// Return definitions for all items inside the [`Module`].
#[cfg(not(feature = "no_module"))]
#[must_use]
fn definition(&self, def: &Definitions) -> String {
let mut s = String::new();
self.write_definition(&mut s, def).unwrap();
@ -455,7 +461,7 @@ impl Module {
|| def
.engine
.custom_keywords
.as_ref()
.as_deref()
.map_or(false, |m| m.contains_key(f.metadata.name.as_str()));
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.
///
/// 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> {
let ty = engine.format_type_name(ty).replace("crate::", "");
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 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")]
#[cfg(not(feature = "no_std"))]
#[cfg(not(target_family = "wasm"))]
@ -137,12 +137,6 @@ impl Engine {
}
/// 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`.
///
/// # Deprecated
@ -191,10 +185,10 @@ impl Engine {
/// This method will be removed in the next major version.
#[deprecated(since = "1.9.1", note = "use `register_fn` instead")]
#[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,
name: impl AsRef<str> + Into<Identifier>,
func: impl RegisterNativeFunction<A, R, RhaiResultOf<S>>,
func: impl RegisterNativeFunction<A, N, C, R, true>,
) -> &mut Self {
self.register_fn(name, func)
}
@ -212,12 +206,10 @@ impl Engine {
#[deprecated(since = "1.9.1", note = "use `register_get` instead")]
#[cfg(not(feature = "no_object"))]
#[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,
name: impl AsRef<str>,
get_fn: impl RegisterNativeFunction<(Mut<T>,), V, RhaiResultOf<S>>
+ crate::func::SendSync
+ 'static,
get_fn: impl RegisterNativeFunction<(Mut<T>,), 1, C, V, true> + crate::func::SendSync + 'static,
) -> &mut Self {
self.register_get(name, get_fn)
}
@ -233,10 +225,10 @@ impl Engine {
#[deprecated(since = "1.9.1", note = "use `register_set` instead")]
#[cfg(not(feature = "no_object"))]
#[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,
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
+ 'static,
) -> &mut Self {
@ -253,12 +245,6 @@ impl Engine {
/// This method is deprecated. Use [`register_indexer_get`][Engine::register_indexer_get] instead.
///
/// 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")]
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[inline(always)]
@ -266,10 +252,10 @@ impl Engine {
T: Variant + Clone,
X: Variant + Clone,
V: Variant + Clone,
S,
const C: bool,
>(
&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
+ 'static,
) -> &mut Self {
@ -284,12 +270,6 @@ impl Engine {
/// This method is deprecated. Use [`register_indexer_set`][Engine::register_indexer_set] instead.
///
/// 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")]
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[inline(always)]
@ -297,10 +277,10 @@ impl Engine {
T: Variant + Clone,
X: Variant + Clone,
V: Variant + Clone,
S,
const C: bool,
>(
&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
+ 'static,
) -> &mut Self {
@ -316,34 +296,6 @@ impl Engine {
/// 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.
///
/// # 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(
since = "1.11.0",
note = "use `register_custom_syntax_with_state_raw` instead"
@ -368,6 +320,24 @@ impl Engine {
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 {
@ -544,10 +514,15 @@ impl<'a, T: Variant + Clone> TypeBuilder<'a, T> {
/// This method will be removed in the next major version.
#[deprecated(since = "1.9.1", note = "use `with_fn` instead")]
#[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
N: AsRef<str> + Into<Identifier>,
F: RegisterNativeFunction<A, R, RhaiResultOf<S>>,
S: AsRef<str> + Into<Identifier>,
R: Variant + Clone,
F: RegisterNativeFunction<A, N, C, R, true>,
{
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")]
#[cfg(not(feature = "no_object"))]
#[inline(always)]
pub fn with_get_result<V: Variant + Clone, S>(
pub fn with_get_result<V: Variant + Clone, const C: bool>(
&mut self,
name: impl AsRef<str>,
get_fn: impl RegisterNativeFunction<(Mut<T>,), V, RhaiResultOf<S>>
+ crate::func::SendSync
+ 'static,
get_fn: impl RegisterNativeFunction<(Mut<T>,), 1, C, V, true> + crate::func::SendSync + 'static,
) -> &mut Self {
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")]
#[cfg(not(feature = "no_object"))]
#[inline(always)]
pub fn with_set_result<V: Variant + Clone, S>(
pub fn with_set_result<V: Variant + Clone, const C: bool>(
&mut self,
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
+ 'static,
) -> &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")]
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[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,
get_fn: impl RegisterNativeFunction<(Mut<T>, X), V, RhaiResultOf<S>>
get_fn: impl RegisterNativeFunction<(Mut<T>, X), 2, C, V, true>
+ crate::func::SendSync
+ 'static,
) -> &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")]
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[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,
set_fn: impl RegisterNativeFunction<(Mut<T>, X, V), (), RhaiResultOf<S>>
set_fn: impl RegisterNativeFunction<(Mut<T>, X, V), 3, C, (), true>
+ crate::func::SendSync
+ 'static,
) -> &mut Self {

View File

@ -5,7 +5,7 @@ use crate::func::native::locked_write;
use crate::parser::ParseState;
use crate::types::dynamic::Variant;
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")]
use std::prelude::v1::*;
@ -121,15 +121,14 @@ impl Engine {
let ast = {
let interned_strings = &mut *locked_write(&self.interned_strings);
let (stream, tokenizer_control) =
self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref));
let (stream, tc) = self.lex_raw(&scripts, self.token_mapper.as_deref());
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
self.parse_global_expr(
&mut stream.peekable(),
&mut state,
stream.peekable(),
state,
|_| {},
#[cfg(not(feature = "no_optimize"))]
OptimizationLevel::None,
@ -243,7 +242,7 @@ impl Engine {
let result = self.eval_global_statements(global, caches, scope, statements);
#[cfg(feature = "debugging")]
if self.debugger.is_some() {
if self.is_debugger_registered() {
global.debugger_mut().status = crate::eval::DebuggerStatus::Terminate;
let mut this = Dynamic::NULL;
let node = &crate::ast::Stmt::Noop(Position::NONE);
@ -263,25 +262,6 @@ impl Engine {
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.

View File

@ -349,7 +349,9 @@ impl Engine {
#[inline(always)]
pub fn register_debugger(
&mut self,
init: impl Fn(&Self) -> Dynamic + SendSync + 'static,
init: impl Fn(&Self, crate::debugger::Debugger) -> crate::debugger::Debugger
+ SendSync
+ 'static,
callback: impl Fn(
EvalContext,
crate::eval::DebuggerEvent,
@ -360,7 +362,7 @@ impl Engine {
+ SendSync
+ 'static,
) -> &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
}
}

View File

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

View File

@ -6,23 +6,20 @@ use std::num::{NonZeroU64, NonZeroUsize};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
pub mod default_limits {
#[cfg(debug_assertions)]
pub mod default_limits {
#[cfg(not(feature = "no_function"))]
pub const MAX_CALL_STACK_DEPTH: usize = 8;
#[cfg(debug_assertions)]
pub const MAX_EXPR_DEPTH: usize = 32;
#[cfg(not(feature = "no_function"))]
#[cfg(debug_assertions)]
pub const MAX_FUNCTION_EXPR_DEPTH: usize = 16;
}
#[cfg(not(debug_assertions))]
pub mod default_limits {
#[cfg(not(feature = "no_function"))]
pub const MAX_CALL_STACK_DEPTH: usize = 64;
#[cfg(not(debug_assertions))]
pub const MAX_EXPR_DEPTH: usize = 64;
#[cfg(not(feature = "no_function"))]
#[cfg(not(debug_assertions))]
pub const MAX_FUNCTION_EXPR_DEPTH: usize = 32;
}
@ -55,7 +52,7 @@ pub struct Limits {
#[cfg(not(feature = "no_module"))]
pub max_modules: usize,
/// 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].
///
/// Not available under `no_index`.
@ -83,7 +80,7 @@ impl Limits {
max_operations: None,
#[cfg(not(feature = "no_module"))]
max_modules: usize::MAX,
max_string_size: None,
max_string_len: None,
#[cfg(not(feature = "no_index"))]
max_array_size: None,
#[cfg(not(feature = "no_object"))]
@ -104,7 +101,7 @@ impl Engine {
/// Is there a data size limit set?
#[inline(always)]
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"))]
{
@ -222,19 +219,19 @@ impl Engine {
#[cfg(feature = "no_function")]
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`.
#[inline(always)]
pub fn set_max_string_size(&mut self, max_size: usize) -> &mut Self {
self.limits.max_string_size = NonZeroUsize::new(max_size);
pub fn set_max_string_size(&mut self, max_len: usize) -> &mut Self {
self.limits.max_string_len = NonZeroUsize::new(max_len);
self
}
/// The maximum length of [strings][crate::ImmutableString] (0 for unlimited).
/// The maximum length, in bytes, of [strings][crate::ImmutableString] (0 for unlimited).
#[inline]
#[must_use]
pub const fn max_string_size(&self) -> usize {
match self.limits.max_string_size {
match self.limits.max_string_len {
Some(n) => n.get(),
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.
pub mod type_names;
pub mod eval;
pub mod run;
@ -21,18 +19,21 @@ pub mod options;
pub mod optimize;
pub mod limits;
pub mod limits_unchecked;
pub mod events;
pub mod custom_syntax;
pub mod type_names;
pub mod deprecated;
pub mod custom_syntax;
pub mod build_type;
#[cfg(feature = "metadata")]
pub mod definitions;
pub mod deprecated;
use crate::{Dynamic, Engine, Identifier};
#[cfg(feature = "no_std")]
@ -167,7 +168,7 @@ impl Engine {
Some(token) if token.is_standard_keyword() => {
if !self
.disabled_symbols
.as_ref()
.as_deref()
.map_or(false, |m| m.contains(token.literal_syntax()))
{
return Err(format!("'{keyword}' is a reserved keyword"));
@ -177,7 +178,7 @@ impl Engine {
Some(token) if token.is_standard_symbol() => {
if !self
.disabled_symbols
.as_ref()
.as_deref()
.map_or(false, |m| m.contains(token.literal_syntax()))
{
return Err(format!("'{keyword}' is a reserved operator"));
@ -187,7 +188,7 @@ impl Engine {
Some(token)
if !self
.disabled_symbols
.as_ref()
.as_deref()
.map_or(false, |m| m.contains(token.literal_syntax())) =>
{
return Err(format!("'{keyword}' is a reserved symbol"))
@ -223,71 +224,3 @@ impl Engine {
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 {
let mut ast = ast;
#[cfg(not(feature = "no_function"))]
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,
let mut _new_ast = self.optimize_into_ast(
scope,
ast.take_statements(),
#[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,
);

View File

@ -56,7 +56,14 @@ impl Engine {
/// # }
/// ```
#[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,
name: impl AsRef<str> + Into<Identifier>,
func: F,
@ -83,13 +90,24 @@ impl Engine {
#[cfg(not(feature = "metadata"))]
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(
name,
FnNamespace::Global,
FnAccess::Public,
param_type_names,
param_types,
func.into_callable_function(),
func,
);
self
}
@ -299,10 +317,10 @@ impl Engine {
/// ```
#[cfg(not(feature = "no_object"))]
#[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,
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 {
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"))]
#[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,
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 {
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"))]
#[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,
name: impl AsRef<str>,
get_fn: impl RegisterNativeFunction<(Mut<T>,), V, S1> + SendSync + 'static,
set_fn: impl RegisterNativeFunction<(Mut<T>, V), (), S2> + SendSync + 'static,
get_fn: impl RegisterNativeFunction<(Mut<T>,), 1, C1, V, L1> + SendSync + 'static,
set_fn: impl RegisterNativeFunction<(Mut<T>, V), 2, C2, (), L2> + SendSync + 'static,
) -> &mut Self {
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")))]
#[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,
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 {
#[cfg(not(feature = "no_index"))]
if TypeId::of::<T>() == TypeId::of::<crate::Array>() {
@ -537,9 +568,15 @@ impl Engine {
/// ```
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[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,
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 {
#[cfg(not(feature = "no_index"))]
if TypeId::of::<T>() == TypeId::of::<crate::Array>() {
@ -616,13 +653,15 @@ impl Engine {
pub fn register_indexer_get_set<
T: Variant + Clone,
X: Variant + Clone,
const C1: bool,
const C2: bool,
V: Variant + Clone,
S1,
S2,
const L1: bool,
const L2: bool,
>(
&mut self,
get_fn: impl RegisterNativeFunction<(Mut<T>, X), V, S1> + SendSync + 'static,
set_fn: impl RegisterNativeFunction<(Mut<T>, X, V), (), S2> + SendSync + 'static,
get_fn: impl RegisterNativeFunction<(Mut<T>, X), 2, C1, V, L1> + SendSync + 'static,
set_fn: impl RegisterNativeFunction<(Mut<T>, X, V), 3, C2, (), L2> + SendSync + 'static,
) -> &mut Self {
self.register_indexer_get(get_fn)
.register_indexer_set(set_fn)
@ -743,7 +782,7 @@ impl Engine {
signatures.extend(self.global_namespace().gen_fn_signatures());
#[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}")));
}

View File

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

View File

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

View File

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

View File

@ -5,6 +5,7 @@ pub mod expr;
pub mod flags;
pub mod ident;
pub mod namespace;
pub mod namespace_none;
pub mod script_fn;
pub mod stmt;
@ -16,6 +17,8 @@ pub use flags::{ASTFlags, FnAccess};
pub use ident::Ident;
#[cfg(not(feature = "no_module"))]
pub use namespace::Namespace;
#[cfg(feature = "no_module")]
pub use namespace_none::Namespace;
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
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`.
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
#[derive(Debug, Clone)]
pub struct EncapsulatedEnviron {
/// Functions defined within the same [`AST`][crate::AST].
@ -24,7 +23,7 @@ pub struct EncapsulatedEnviron {
/// Imported [modules][crate::Module].
pub imports: Box<[(ImmutableString, crate::SharedModule)]>,
/// 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.
@ -35,7 +34,6 @@ pub struct ScriptFnDef {
pub body: StmtBlock,
/// Encapsulated AST environment, if any.
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
pub environ: Option<crate::Shared<EncapsulatedEnviron>>,
/// Function name.
pub name: ImmutableString,
@ -51,12 +49,14 @@ pub struct ScriptFnDef {
///
/// 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
/// corresponding doc-comment leader: `///` or `/**`.
///
/// Each line in non-block doc-comments starts with `///`.
#[cfg(feature = "metadata")]
pub comments: Box<[Box<str>]>,
pub comments: Box<[crate::Identifier]>,
}
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.
///
/// 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
/// corresponding doc-comment leader: `///` or `/**`.
///
/// Each line in non-block doc-comments starts with `///`.
#[cfg(feature = "metadata")]
pub comments: Vec<&'a str>,
}

View File

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

View File

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

View File

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

View File

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

View File

@ -147,7 +147,7 @@ pub struct Engine {
/// Callback closure for debugging.
#[cfg(feature = "debugging")]
pub(crate) debugger: Option<
pub(crate) debugger_interface: Option<
Box<(
Box<crate::eval::OnDebuggingInit>,
Box<crate::eval::OnDebuggerCallback>,
@ -191,6 +191,9 @@ impl fmt::Debug for Engine {
#[cfg(not(feature = "unchecked"))]
f.field("limits", &self.limits);
#[cfg(feature = "debugging")]
f.field("debugger_interface", &self.debugger_interface.is_some());
f.finish()
}
}
@ -305,7 +308,7 @@ impl Engine {
limits: crate::api::limits::Limits::new(),
#[cfg(feature = "debugging")]
debugger: None,
debugger_interface: None,
};
// Add the global namespace module
@ -348,4 +351,12 @@ impl Engine {
pub fn const_empty_string(&self) -> ImmutableString {
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::config::hashing::SusLock;
use crate::engine::{FN_IDX_GET, FN_IDX_SET};
use crate::tokenizer::NO_TOKEN;
use crate::types::dynamic::Union;
use crate::types::RestoreOnDrop;
use crate::{
calc_fn_hash, Dynamic, Engine, FnArgsVec, Position, RhaiResult, RhaiResultOf, Scope, ERR,
};
@ -68,20 +68,12 @@ impl Engine {
idx: &mut Dynamic,
pos: Position,
) -> RhaiResultOf<Dynamic> {
let orig_level = global.level;
global.level += 1;
let global = &mut *RestoreOnDrop::lock(global, move |g| g.level = orig_level);
auto_restore! { let orig_level = global.level; global.level += 1 }
self.exec_native_fn_call(
global,
caches,
FN_IDX_GET,
None,
hash_idx().0,
&mut [target, idx],
true,
pos,
)
let hash = hash_idx().0;
let args = &mut [target, idx];
self.exec_native_fn_call(global, caches, FN_IDX_GET, NO_TOKEN, hash, args, true, pos)
.map(|(r, ..)| r)
}
@ -97,19 +89,13 @@ impl Engine {
is_ref_mut: bool,
pos: Position,
) -> RhaiResultOf<(Dynamic, bool)> {
let orig_level = global.level;
global.level += 1;
let global = &mut *RestoreOnDrop::lock(global, move |g| g.level = orig_level);
auto_restore! { let orig_level = global.level; global.level += 1 }
let hash = hash_idx().1;
let args = &mut [target, idx, new_val];
self.exec_native_fn_call(
global,
caches,
FN_IDX_SET,
None,
hash_idx().1,
&mut [target, idx, new_val],
is_ref_mut,
pos,
global, caches, FN_IDX_SET, NO_TOKEN, hash, args, is_ref_mut, pos,
)
}
@ -599,7 +585,9 @@ impl Engine {
// Try to call index setter if value is changed
let idx = &mut idx_val_for_setter;
let new_val = &mut new_val;
self.call_indexer_set(
// The return value of a indexer setter (usually `()`) is thrown away and not used.
let _ = self
.call_indexer_set(
global, caches, target, idx, new_val, is_ref_mut, op_pos,
)
.or_else(|e| match *e {
@ -659,8 +647,8 @@ impl Engine {
// Try to call index setter
let new_val = &mut new_val;
self.call_indexer_set(
// The return value of a indexer setter (usually `()`) is thrown away and not used.
let _ = self.call_indexer_set(
global, caches, target, idx_val, new_val, is_ref_mut, op_pos,
)?;
}
@ -696,19 +684,17 @@ impl Engine {
let reset =
self.run_debugger_with_reset(global, caches, scope, this_ptr, rhs)?;
#[cfg(feature = "debugging")]
let global =
&mut *RestoreOnDrop::lock_if(reset.is_some(), global, move |g| {
g.debugger_mut().reset_status(reset)
});
auto_restore!(global if reset.is_some() => move |g| g.debugger_mut().reset_status(reset));
let crate::ast::FnCallExpr {
name, hashes, args, ..
} = &**x;
// Truncate the index values upon exit
auto_restore! {
idx_values => truncate;
let offset = idx_values.len() - args.len();
let idx_values =
&mut *RestoreOnDrop::lock(idx_values, move |v| v.truncate(offset));
}
let call_args = &mut idx_values[offset..];
let arg1_pos = args.get(0).map_or(Position::NONE, Expr::position);
@ -764,9 +750,11 @@ impl Engine {
if op_info.is_op_assignment() {
let args = &mut [target.as_mut()];
let (mut orig_val, ..) = 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,
)
.or_else(|err| match *err {
// Try an indexer if property does not exist
@ -798,8 +786,9 @@ impl Engine {
}
let args = &mut [target.as_mut(), &mut new_val];
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 {
// Try an indexer if property does not exist
@ -824,8 +813,9 @@ impl Engine {
let ((getter, hash_get), _, name) = &**x;
let args = &mut [target.as_mut()];
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(
|err| match *err {
@ -866,21 +856,17 @@ impl Engine {
global, caches, scope, this_ptr, _node,
)?;
#[cfg(feature = "debugging")]
let global = &mut *RestoreOnDrop::lock_if(
reset.is_some(),
global,
move |g| g.debugger_mut().reset_status(reset),
);
auto_restore!(global if reset.is_some() => move |g| g.debugger_mut().reset_status(reset));
let crate::ast::FnCallExpr {
name, hashes, args, ..
} = &**x;
// Truncate the index values upon exit
auto_restore! {
idx_values => truncate;
let offset = idx_values.len() - args.len();
let idx_values = &mut *RestoreOnDrop::lock(idx_values, move |v| {
v.truncate(offset)
});
}
let call_args = &mut idx_values[offset..];
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)?;
let ((getter, hash_get), (setter, hash_set), name) = &**p;
let mut arg_values = [target.as_mut(), &mut Dynamic::UNIT.clone()];
let args = &mut arg_values[..1];
let args = &mut [target.as_mut()];
// Assume getters are always pure
let (mut val, ..) = 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,
)
.or_else(|err| match *err {
// 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
if may_be_changed {
// 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 arg_values;
self.exec_native_fn_call(
global, caches, setter, None, *hash_set, args, is_ref_mut,
pos,
let args = &mut [target.as_mut(), val.as_mut()];
// The return value is thrown away and not used.
let _ = self
.exec_native_fn_call(
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
ERR::ErrorDotExpr(..) => {
let idx = &mut name.into();
@ -978,8 +964,7 @@ impl Engine {
})
}
_ => Err(err),
},
)?;
})?;
}
Ok((result, may_be_changed))
@ -992,22 +977,17 @@ impl Engine {
global, caches, scope, this_ptr, _node,
)?;
#[cfg(feature = "debugging")]
let global = &mut *RestoreOnDrop::lock_if(
reset.is_some(),
global,
move |g| g.debugger_mut().reset_status(reset),
);
auto_restore!(global if reset.is_some() => move |g| g.debugger_mut().reset_status(reset));
let crate::ast::FnCallExpr {
name, hashes, args, ..
} = &**f;
// Truncate the index values upon exit
auto_restore! {
idx_values => truncate;
let offset = idx_values.len() - args.len();
let idx_values =
&mut *RestoreOnDrop::lock(idx_values, move |v| {
v.truncate(offset)
});
}
let call_args = &mut idx_values[offset..];
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]
/// and should be set afterwards.
pub(crate) fn raise_err_if_over_data_size_limit(
&self,
(_arr, _map, s): (usize, usize, usize),
) -> RhaiResultOf<()> {
pub(crate) fn throw_on_size(&self, (_arr, _map, s): (usize, usize, usize)) -> RhaiResultOf<()> {
if self
.limits
.max_string_size
.max_string_len
.map_or(false, |max| s > max.get())
{
return Err(
@ -127,9 +124,10 @@ impl Engine {
let sizes = value.borrow().calc_data_sizes(true);
self.raise_err_if_over_data_size_limit(sizes)
.map(|_| value)
.map_err(|err| err.fill_position(pos))
self.throw_on_size(sizes)
.map_err(|err| err.fill_position(pos))?;
Ok(value)
}
/// 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.
#[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.
#[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.
#[cfg(not(feature = "sync"))]
@ -268,12 +268,12 @@ impl Debugger {
/// Create a new [`Debugger`].
#[inline(always)]
#[must_use]
pub const fn new(status: DebuggerStatus, state: Dynamic) -> Self {
pub const fn new(status: DebuggerStatus) -> Self {
Self {
status,
break_points: Vec::new(),
call_stack: Vec::new(),
state,
state: Dynamic::UNIT,
}
}
/// Get the current call stack.
@ -415,7 +415,7 @@ impl Engine {
this_ptr: &mut Dynamic,
node: impl Into<ASTNode<'a>>,
) -> RhaiResultOf<()> {
if self.debugger.is_some() {
if self.is_debugger_registered() {
if let Some(cmd) =
self.run_debugger_with_reset_raw(global, caches, scope, this_ptr, node)?
{
@ -440,7 +440,7 @@ impl Engine {
this_ptr: &mut Dynamic,
node: impl Into<ASTNode<'a>>,
) -> 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)
} else {
Ok(None)
@ -508,11 +508,10 @@ impl Engine {
node: ASTNode<'a>,
event: DebuggerEvent,
) -> Result<Option<DebuggerStatus>, Box<crate::EvalAltResult>> {
if let Some(ref x) = self.debugger_interface {
let src = global.source_raw().cloned();
let src = src.as_ref().map(|s| s.as_str());
let context = crate::EvalContext::new(self, global, caches, scope, this_ptr);
if let Some(ref x) = self.debugger {
let context = EvalContext::new(self, global, caches, scope, this_ptr);
let (.., ref on_debugger) = **x;
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::*;
/// Context of a script evaluation process.
#[derive(Debug)]
#[allow(dead_code)]
pub struct EvalContext<'a, 's, 'ps, 'g, 'c, 't> {
/// The current [`Engine`].

View File

@ -62,25 +62,22 @@ impl Engine {
}
Expr::Variable(v, None, ..) => match &**v {
// Normal variable access
#[cfg(not(feature = "no_module"))]
(_, ns, ..) if ns.is_empty() => {
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
#[cfg(not(feature = "no_module"))]
(_, namespace, hash_var, var_name) => {
(_, ns, hash_var, var_name) => {
// 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(
|| {
let sep = crate::tokenizer::Token::DoubleColon.literal_syntax();
Err(ERR::ErrorVariableNotFound(
format!("{namespace}{sep}{var_name}"),
namespace.position(),
format!("{ns}{sep}{var_name}"),
ns.position(),
)
.into())
},
@ -94,7 +91,7 @@ impl Engine {
// global::VARIABLE
#[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(value) =
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();
return Err(ERR::ErrorVariableNotFound(
format!("{namespace}{sep}{var_name}"),
namespace.position(),
format!("{ns}{sep}{var_name}"),
ns.position(),
)
.into());
}
Err(
ERR::ErrorModuleNotFound(namespace.to_string(), namespace.position())
.into(),
)
Err(ERR::ErrorModuleNotFound(ns.to_string(), ns.position()).into())
}
#[cfg(feature = "no_module")]
_ => unreachable!("Invalid expression {:?}", expr),
},
_ => unreachable!("Expr::Variable expected but gets {:?}", expr),
}
@ -142,14 +139,18 @@ impl Engine {
let index = match expr {
// 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() {
Err(ERR::ErrorUnboundThis(expr.position()).into())
} else {
Ok(this_ptr.into())
};
}
_ if global.always_search_scope => 0,
Expr::Variable(_, Some(i), ..) => i.get() as usize,
// Scripted function with the same name
#[cfg(not(feature = "no_function"))]
@ -165,6 +166,7 @@ impl Engine {
return Ok(val.into());
}
Expr::Variable(v, None, ..) => v.0.map_or(0, NonZeroUsize::get),
_ => unreachable!("Expr::Variable expected but gets {:?}", expr),
};
@ -232,10 +234,7 @@ impl Engine {
#[cfg(feature = "debugging")]
let reset = self.run_debugger_with_reset(global, caches, scope, this_ptr, expr)?;
#[cfg(feature = "debugging")]
let global =
&mut *crate::types::RestoreOnDrop::lock_if(reset.is_some(), global, move |g| {
g.debugger_mut().reset_status(reset)
});
auto_restore!(global if reset.is_some() => move |g| g.debugger_mut().reset_status(reset));
self.track_operation(global, expr.position())?;
@ -266,10 +265,7 @@ impl Engine {
#[cfg(feature = "debugging")]
let reset = self.run_debugger_with_reset(global, caches, scope, this_ptr, expr)?;
#[cfg(feature = "debugging")]
let global =
&mut *crate::types::RestoreOnDrop::lock_if(reset.is_some(), global, move |g| {
g.debugger_mut().reset_status(reset)
});
auto_restore!(global if reset.is_some() => move |g| g.debugger_mut().reset_status(reset));
self.track_operation(global, expr.position())?;
@ -327,7 +323,7 @@ impl Engine {
total_data_sizes.1 + val_sizes.1,
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()))?;
}
@ -358,7 +354,7 @@ impl Engine {
total_data_sizes.1 + delta.1,
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()))?;
}

View File

@ -8,7 +8,7 @@ use std::prelude::v1::*;
/// Collection of globally-defined constants.
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
pub type GlobalConstants =
pub type SharedGlobalConstants =
crate::Shared<crate::Locked<std::collections::BTreeMap<ImmutableString, Dynamic>>>;
/// _(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.
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
pub constants: Option<GlobalConstants>,
pub constants: Option<SharedGlobalConstants>,
/// Custom state that can be used by the external host.
pub tag: Dynamic,
/// Debugging interface.
#[cfg(feature = "debugging")]
pub(crate) debugger: Option<super::Debugger>,
pub(crate) debugger: Option<Box<super::Debugger>>,
}
impl GlobalRuntimeState {
@ -103,8 +103,9 @@ impl GlobalRuntimeState {
tag: engine.default_tag().clone(),
#[cfg(feature = "debugging")]
debugger: engine.debugger.as_ref().map(|x| {
crate::eval::Debugger::new(crate::eval::DebuggerStatus::Init, (x.0)(engine))
debugger: engine.debugger_interface.as_ref().map(|x| {
let dbg = crate::eval::Debugger::new(crate::eval::DebuggerStatus::Init);
(x.0)(engine, dbg).into()
}),
}
}
@ -115,7 +116,7 @@ impl GlobalRuntimeState {
#[inline]
#[must_use]
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.
///
@ -138,7 +139,7 @@ impl GlobalRuntimeState {
&mut self,
index: usize,
) -> 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.
///
@ -183,8 +184,8 @@ impl GlobalRuntimeState {
self.imports = None;
self.modules = None;
} else if self.imports.is_some() {
self.imports.as_mut().unwrap().truncate(size);
self.modules.as_mut().unwrap().truncate(size);
self.imports.as_deref_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.
@ -194,10 +195,11 @@ impl GlobalRuntimeState {
#[inline]
pub fn iter_imports(&self) -> impl Iterator<Item = (&str, &crate::Module)> {
self.imports
.iter()
.flat_map(|x| x.iter())
.as_deref()
.into_iter()
.flatten()
.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))
}
/// Get an iterator to the stack of globally-imported [modules][crate::Module] in reverse order.
@ -209,10 +211,11 @@ impl GlobalRuntimeState {
&self,
) -> impl Iterator<Item = (&ImmutableString, &crate::SharedModule)> {
self.imports
.iter()
.flat_map(|x| x.iter())
.as_deref()
.into_iter()
.flatten()
.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.
///
@ -223,9 +226,10 @@ impl GlobalRuntimeState {
&self,
) -> impl Iterator<Item = (&ImmutableString, &crate::SharedModule)> {
self.imports
.iter()
.flat_map(|x| x.iter())
.zip(self.modules.iter().flat_map(|x| x.iter()))
.as_deref()
.into_iter()
.flatten()
.zip(self.modules.as_deref().into_iter().flatten())
}
/// Can the particular function with [`Dynamic`] parameter(s) exist in the stack of
/// globally-imported [modules][crate::Module]?
@ -234,7 +238,7 @@ impl GlobalRuntimeState {
#[cfg(not(feature = "no_module"))]
#[inline]
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))
})
}
@ -323,7 +327,7 @@ impl GlobalRuntimeState {
/// Panics if the debugging interface is not set.
#[cfg(feature = "debugging")]
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,
};
pub use eval_context::EvalContext;
pub use global_state::GlobalRuntimeState;
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
pub use global_state::GlobalConstants;
pub use global_state::GlobalRuntimeState;
pub use global_state::SharedGlobalConstants;
#[cfg(not(feature = "no_index"))]
pub use target::calc_offset_len;
pub use target::{calc_index, Target};

View File

@ -3,16 +3,28 @@
use super::{Caches, EvalContext, GlobalRuntimeState, Target};
use crate::api::events::VarDefInfo;
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::types::dynamic::AccessMode;
use crate::types::RestoreOnDrop;
use crate::types::dynamic::{AccessMode, Union};
use crate::{Dynamic, Engine, RhaiResult, RhaiResultOf, Scope, ERR, INT};
use std::hash::{Hash, Hasher};
#[cfg(feature = "no_std")]
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 {
/// Evaluate a statements block.
pub(crate) fn eval_stmt_block(
@ -29,10 +41,10 @@ impl Engine {
}
// Restore scope at end of block if necessary
auto_restore! {
scope if restore_orig_state => rewind;
let orig_scope_len = scope.len();
let scope = &mut *RestoreOnDrop::lock_if(restore_orig_state, scope, move |s| {
s.rewind(orig_scope_len);
});
}
// Restore global state at end of block if necessary
let orig_always_search_scope = global.always_search_scope;
@ -43,7 +55,7 @@ impl Engine {
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;
#[cfg(not(feature = "no_module"))]
@ -55,10 +67,10 @@ impl Engine {
});
// Pop new function resolution caches at end of block
auto_restore! {
caches => rewind_fn_resolution_caches;
let orig_fn_resolution_caches_len = caches.fn_resolution_caches_len();
let caches = &mut *RestoreOnDrop::lock(caches, move |c| {
c.rewind_fn_resolution_caches(orig_fn_resolution_caches_len)
});
}
// Run the statements
statements.iter().try_fold(Dynamic::UNIT, |_, stmt| {
@ -129,23 +141,25 @@ impl Engine {
let args = &mut [&mut *lock_guard, &mut new_val];
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
let op = op_assign_token.literal_syntax();
auto_restore! { let orig_level = global.level; global.level += 1 }
let orig_level = global.level;
global.level += 1;
let global = &*RestoreOnDrop::lock(global, move |g| g.level = orig_level);
let context = (self, op, None, global, *op_pos).into();
let context = if ctx {
Some((self, op, None, &*global, *op_pos).into())
} else {
None
};
return func(context, args).map(|_| ());
}
}
let op_assign = op_assign_token.literal_syntax();
let op = op_token.literal_syntax();
let token = Some(op_assign_token);
let token = op_assign_token.clone();
match self
.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)) =>
{
// Expand to `var = var op rhs`
let token = Some(op_token);
let token = op_token.clone();
*args[0] = self
.exec_native_fn_call(
@ -168,14 +182,14 @@ impl Engine {
self.check_data_size(&*args[0], root.position())?;
} else {
// Normal assignment
// If value is a string, intern it
if new_val.is_string() {
let value = new_val.into_immutable_string().expect("`ImmutableString`");
new_val = self.get_interned_string(value).into();
match target {
// Lock it again just in case it is shared
Target::RefMut(_) | Target::TempValue(_) => {
*target.write_lock::<Dynamic>().unwrap() = new_val
}
#[allow(unreachable_patterns)]
_ => **target = new_val,
}
*target.write_lock::<Dynamic>().unwrap() = new_val;
}
target.propagate_changed_value(op_info.pos)
@ -194,9 +208,7 @@ impl Engine {
#[cfg(feature = "debugging")]
let reset = self.run_debugger_with_reset(global, caches, scope, this_ptr, stmt)?;
#[cfg(feature = "debugging")]
let global = &mut *RestoreOnDrop::lock_if(reset.is_some(), global, move |g| {
g.debugger_mut().reset_status(reset)
});
auto_restore!(global if reset.is_some() => move |g| g.debugger_mut().reset_status(reset));
// Coded this way for better branch prediction.
// 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 is_temp_result = !target.is_ref();
let var_name = x.3.as_str();
#[cfg(not(feature = "no_closure"))]
// Also handle case where target is a `Dynamic` shared value
// (returned by a variable resolver, for example)
let is_temp_result = !target.is_ref() && !target.is_shared();
#[cfg(feature = "no_closure")]
let is_temp_result = !target.is_ref();
let is_temp_result = is_temp_result && !target.is_shared();
// Cannot assign to temp result from expression
if is_temp_result {
@ -250,19 +261,16 @@ impl Engine {
#[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)?
.flatten();
// 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();
}
.flatten()
.intern_string(self);
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`.
// The return value of any op-assignment (should be `()`) is thrown away and not used.
let _ =
match lhs {
// name op= rhs (handled above)
Expr::Variable(..) => {
@ -270,14 +278,12 @@ impl Engine {
}
// idx_lhs[idx_expr] op= rhs
#[cfg(not(feature = "no_index"))]
Expr::Index(..) => {
self.eval_dot_index_chain(global, caches, scope, this_ptr, lhs, _new_val)
}
Expr::Index(..) => self
.eval_dot_index_chain(global, caches, scope, this_ptr, lhs, _new_val),
// dot_lhs.dot_rhs op= rhs
#[cfg(not(feature = "no_object"))]
Expr::Dot(..) => {
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),
}?;
@ -494,27 +500,29 @@ impl Engine {
// 2) Global modules - packages
// 3) Imported modules - functions marked with global namespace
// 4) Global sub-modules - functions marked with global namespace
let func = self
let iter_func = self
.global_modules
.iter()
.find_map(|m| m.get_iter(iter_type));
#[cfg(not(feature = "no_module"))]
let func = func.or_else(|| global.get_iter(iter_type)).or_else(|| {
let iter_func = iter_func
.or_else(|| global.get_iter(iter_type))
.or_else(|| {
self.global_sub_modules
.iter()
.flat_map(|m| m.values())
.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
auto_restore! {
scope => rewind;
let orig_scope_len = scope.len();
let scope = &mut *RestoreOnDrop::lock(scope, move |s| {
s.rewind(orig_scope_len);
});
}
// Add the loop variables
let counter_index = if counter.is_empty() {
usize::MAX
@ -528,7 +536,7 @@ impl Engine {
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
if counter_index < usize::MAX {
// As the variable increments from 0, this should always work
@ -596,10 +604,7 @@ impl Engine {
Stmt::TryCatch(x, ..) => {
let TryCatchBlock {
try_block,
catch_var:
Ident {
name: catch_var, ..
},
catch_var,
catch_block,
} = &**x;
@ -644,14 +649,13 @@ impl Engine {
};
// Restore scope at end of block
auto_restore! {
scope if !catch_var.is_empty() => rewind;
let orig_scope_len = scope.len();
let scope =
&mut *RestoreOnDrop::lock_if(!catch_var.is_empty(), scope, move |s| {
s.rewind(orig_scope_len);
});
}
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)
@ -721,7 +725,8 @@ impl Engine {
// Evaluate initial value
let mut value = self
.eval_expr(global, caches, scope, this_ptr, expr)?
.flatten();
.flatten()
.intern_string(self);
let _alias = if !rewind_scope {
// Put global constants into global module
@ -759,7 +764,7 @@ impl Engine {
#[cfg(not(feature = "no_module"))]
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)
@ -826,6 +831,7 @@ impl Engine {
// Export statement
#[cfg(not(feature = "no_module"))]
Stmt::Export(x, ..) => {
use crate::ast::Ident;
let (Ident { name, pos, .. }, Ident { name: alias, .. }) = &**x;
// Mark scope variables as public
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.
#[derive(Debug)]
#[must_use]
pub enum Target<'a> {
/// The target is a mutable reference to a [`Dynamic`].
RefMut(&'a mut Dynamic),
@ -88,9 +89,9 @@ pub enum Target<'a> {
#[cfg(not(feature = "no_closure"))]
SharedValue {
/// Lock guard to the shared [`Dynamic`].
source: crate::types::dynamic::DynamicWriteLock<'a, Dynamic>,
/// Copy of the value.
value: Dynamic,
guard: crate::types::dynamic::DynamicWriteLock<'a, Dynamic>,
/// Copy of the shared value.
shared_value: Dynamic,
},
/// The target is a temporary [`Dynamic`] value (i.e. its mutation can cause no side effects).
TempValue(Dynamic),
@ -177,13 +178,12 @@ impl<'a> Target<'a> {
}
}
/// Is the [`Target`] a shared value?
#[cfg(not(feature = "no_closure"))]
#[inline]
#[must_use]
pub fn is_shared(&self) -> bool {
match self {
Self::RefMut(r) => r.is_shared(),
#[cfg(not(feature = "no_closure"))]
return match self {
Self::RefMut(r) => r.is_shared(),
Self::SharedValue { .. } => true,
Self::TempValue(value) => value.is_shared(),
#[cfg(not(feature = "no_index"))]
@ -191,16 +191,17 @@ impl<'a> Target<'a> {
| Self::BitField { .. }
| Self::BlobByte { .. }
| Self::StringChar { .. } => false,
}
};
#[cfg(feature = "no_closure")]
return false;
}
/// Get the value of the [`Target`] as a [`Dynamic`], cloning a referenced value if necessary.
#[inline]
#[must_use]
pub fn take_or_clone(self) -> Dynamic {
match self {
Self::RefMut(r) => r.clone(), // Referenced value is cloned
#[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
#[cfg(not(feature = "no_index"))]
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.
#[inline(always)]
#[must_use]
pub fn into_owned(self) -> Self {
match self {
Self::RefMut(r) => Self::TempValue(r.clone()),
#[cfg(not(feature = "no_closure"))]
Self::SharedValue { value, .. } => Self::TempValue(value),
Self::SharedValue { shared_value, .. } => Self::TempValue(shared_value),
_ => self,
}
}
@ -240,7 +240,7 @@ impl<'a> Target<'a> {
match self {
Self::RefMut(r) => r,
#[cfg(not(feature = "no_closure"))]
Self::SharedValue { source, .. } => source,
Self::SharedValue { guard, .. } => guard,
Self::TempValue(value) => value,
#[cfg(not(feature = "no_index"))]
Self::Bit { source, .. } => source,
@ -366,9 +366,12 @@ impl<'a> From<&'a mut Dynamic> for Target<'a> {
#[cfg(not(feature = "no_closure"))]
if value.is_shared() {
// Cloning is cheap for a shared value
let val = value.clone();
let source = value.write_lock::<Dynamic>().expect("`Dynamic`");
return Self::SharedValue { source, value: val };
let shared_value = value.clone();
let guard = value.write_lock::<Dynamic>().expect("`Dynamic`");
return Self::SharedValue {
guard,
shared_value,
};
}
Self::RefMut(value)
@ -383,7 +386,7 @@ impl Deref for Target<'_> {
match self {
Self::RefMut(r) => r,
#[cfg(not(feature = "no_closure"))]
Self::SharedValue { source, .. } => source,
Self::SharedValue { guard, .. } => guard,
Self::TempValue(ref value) => value,
#[cfg(not(feature = "no_index"))]
Self::Bit { ref value, .. }
@ -416,7 +419,7 @@ impl DerefMut for Target<'_> {
match self {
Self::RefMut(r) => r,
#[cfg(not(feature = "no_closure"))]
Self::SharedValue { source, .. } => &mut *source,
Self::SharedValue { guard, .. } => &mut *guard,
Self::TempValue(ref mut value) => value,
#[cfg(not(feature = "no_index"))]
Self::Bit { ref mut value, .. }
@ -437,7 +440,6 @@ impl AsMut<Dynamic> for Target<'_> {
impl<T: Into<Dynamic>> From<T> for Target<'_> {
#[inline(always)]
#[must_use]
fn from(value: T) -> Self {
Self::TempValue(value.into())
}

View File

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

View File

@ -8,8 +8,7 @@ use crate::engine::{
KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
};
use crate::eval::{Caches, FnResolutionCacheEntry, GlobalRuntimeState};
use crate::tokenizer::{is_valid_function_name, Token};
use crate::types::RestoreOnDrop;
use crate::tokenizer::{is_valid_function_name, Token, NO_TOKEN};
use crate::{
calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnArgsVec, FnPtr, ImmutableString,
OptimizationLevel, Position, RhaiError, RhaiResult, RhaiResultOf, Scope, Shared, ERR,
@ -165,7 +164,7 @@ impl Engine {
_global: &GlobalRuntimeState,
caches: &'s mut Caches,
local_entry: &'s mut Option<FnResolutionCacheEntry>,
op_token: Option<&Token>,
op_token: Token,
hash_base: u64,
args: Option<&mut FnCallArgs>,
allow_dynamic: bool,
@ -174,7 +173,7 @@ impl Engine {
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()))
});
@ -183,7 +182,7 @@ impl Engine {
match cache.map.entry(hash) {
Entry::Occupied(entry) => entry.into_mut().as_ref(),
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.
// Set later when a specific matching function is not found.
let mut bitmask = 1usize; // Bitmask of which parameter to replace with `Dynamic`
@ -212,9 +211,12 @@ impl Engine {
} else {
func.or_else(|| _global.get_qualified_fn(hash)).or_else(|| {
self.global_sub_modules
.iter()
.flat_map(|m| m.values())
.find_map(|m| m.get_qualified_fn(hash).map(|f| (f, m.id_raw())))
.as_deref()
.into_iter()
.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"))]
let is_dynamic = is_dynamic
|| _global.may_contain_dynamic_fn(hash_base)
|| self
.global_sub_modules
.iter()
.flat_map(|m| m.values())
.any(|m| m.may_contain_dynamic_fn(hash_base));
|| self.global_sub_modules.as_deref().map_or(false, |m| {
m.values().any(|m| m.may_contain_dynamic_fn(hash_base))
});
// Set maximum bitmask when there are dynamic versions of the function
if is_dynamic {
@ -272,22 +272,22 @@ impl Engine {
// Try to find a built-in version
let builtin =
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();
get_builtin_op_assignment_fn(token, first_arg, rest_args[0])
.map(|f| FnResolutionCacheEntry {
func: CallableFunction::Method(Shared::new(f)),
.map(|(f, ctx)| FnResolutionCacheEntry {
func: CallableFunction::Method(Shared::new(f), ctx),
source: None,
})
}
Some(token) => get_builtin_binary_op_fn(token, args[0], args[1])
.map(|f| FnResolutionCacheEntry {
func: CallableFunction::Method(Shared::new(f)),
token => get_builtin_binary_op_fn(token, args[0], args[1]).map(
|(f, ctx)| FnResolutionCacheEntry {
func: CallableFunction::Method(Shared::new(f), ctx),
source: None,
}),
None => None,
},
),
});
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
///
@ -340,7 +340,7 @@ impl Engine {
global: &mut GlobalRuntimeState,
caches: &mut Caches,
name: &str,
op_token: Option<&Token>,
op_token: Token,
hash: u64,
args: &mut FnCallArgs,
is_ref_mut: bool,
@ -380,12 +380,10 @@ impl Engine {
// Clone the first argument
backup.change_first_arg_to_copy(args);
}
let args =
&mut *RestoreOnDrop::lock_if(swap, args, move |a| backup.restore_first_arg(a));
auto_restore!(args if swap => move |a| backup.restore_first_arg(a));
#[cfg(feature = "debugging")]
if self.debugger.is_some() {
if self.is_debugger_registered() {
let source = source.clone().or_else(|| global.source.clone());
global.debugger_mut().push_call_stack_frame(
@ -399,25 +397,29 @@ impl Engine {
// Run external function
let is_method = func.is_method();
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 f = func.get_plugin_fn().unwrap();
let context = if func.has_context() {
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() {
Err(ERR::ErrorNonPureMethodCallOnConstant(name.to_string(), pos).into())
} else {
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 {
func.get_native_fn().unwrap()(context, args)
unreachable!();
}
.and_then(|r| self.check_data_size(r, pos))
.map_err(|err| err.fill_position(pos))
};
.map_err(|err| err.fill_position(pos));
#[cfg(feature = "debugging")]
if self.debugger.is_some() {
if self.is_debugger_registered() {
use crate::eval::{DebuggerEvent, DebuggerStatus};
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
///
@ -559,7 +561,7 @@ impl Engine {
caches: &mut Caches,
_scope: Option<&mut Scope>,
fn_name: &str,
op_token: Option<&Token>,
op_token: Token,
hashes: FnCallHashes,
mut _args: &mut FnCallArgs,
is_ref_mut: bool,
@ -578,9 +580,7 @@ impl Engine {
#[cfg(not(feature = "no_closure"))]
ensure_no_data_race(fn_name, _args, is_ref_mut)?;
let orig_level = global.level;
global.level += 1;
let global = &mut *RestoreOnDrop::lock(global, move |g| g.level = orig_level);
auto_restore! { let orig_level = global.level; global.level += 1 }
// These may be redirected from method style calls.
if hashes.is_native_only() {
@ -638,7 +638,7 @@ impl Engine {
let local_entry = &mut None;
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()
{
// Script function call
@ -659,7 +659,7 @@ impl Engine {
};
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 {
// Method call of script function - map first argument to `this`
@ -679,9 +679,7 @@ impl Engine {
backup.change_first_arg_to_copy(_args);
}
let args = &mut *RestoreOnDrop::lock_if(swap, _args, move |a| {
backup.restore_first_arg(a)
});
auto_restore!(args = (_args) if swap => move |a| backup.restore_first_arg(a));
let mut this = Dynamic::NULL;
@ -721,15 +719,13 @@ impl Engine {
// Do not match function exit for arguments
#[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| {
matches!(status, crate::eval::DebuggerStatus::FunctionExit(..))
})
});
#[cfg(feature = "debugging")]
let global = &mut *RestoreOnDrop::lock_if(reset.is_some(), global, move |g| {
g.debugger_mut().reset_status(reset)
});
auto_restore!(global if reset.is_some() => move |g| g.debugger_mut().reset_status(reset));
self.eval_expr(global, caches, scope, this_ptr, arg_expr)
.map(|r| (r, arg_expr.start_position()))
@ -782,7 +778,7 @@ impl Engine {
caches,
None,
fn_name,
None,
NO_TOKEN,
new_hash,
&mut args,
false,
@ -836,7 +832,7 @@ impl Engine {
caches,
None,
&fn_name,
None,
NO_TOKEN,
new_hash,
&mut args,
is_ref_mut,
@ -935,7 +931,7 @@ impl Engine {
caches,
None,
fn_name,
None,
NO_TOKEN,
hash,
&mut args,
is_ref_mut,
@ -961,7 +957,7 @@ impl Engine {
scope: &mut Scope,
this_ptr: &mut Dynamic,
fn_name: &str,
op_token: Option<&Token>,
op_token: Token,
first_arg: Option<&Expr>,
args_expr: &[Expr],
hashes: FnCallHashes,
@ -977,7 +973,7 @@ impl Engine {
let redirected; // Handle call() - Redirect function call
match name {
_ if op_token.is_some() => (),
_ if op_token != NO_TOKEN => (),
// Handle call()
KEYWORD_FN_PTR_CALL if total_args >= 1 => {
@ -1208,12 +1204,7 @@ impl Engine {
self.track_operation(global, first_expr.position())?;
#[cfg(not(feature = "no_closure"))]
let target_is_shared = target.is_shared();
#[cfg(feature = "no_closure")]
let target_is_shared = false;
if target_is_shared || target.is_temp_value() {
if target.is_shared() || target.is_temp_value() {
arg_values.insert(0, target.take_or_clone().flatten());
} else {
// 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;
global.level += 1;
let global = &mut *RestoreOnDrop::lock(global, move |g| g.level = orig_level);
auto_restore! { let orig_level = global.level; global.level += 1 }
match func {
#[cfg(not(feature = "no_function"))]
@ -1382,7 +1371,7 @@ impl Engine {
let mut this = Dynamic::NULL;
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(
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() => {
let context = (self, fn_name, module.id(), &*global, pos).into();
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() {
Err(ERR::ErrorNonPureMethodCallOnConstant(fn_name.to_string(), pos).into())
} else {
@ -1402,7 +1395,11 @@ impl Engine {
Some(f) if f.is_native() => {
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))
}
@ -1467,6 +1464,8 @@ impl Engine {
self.eval_global_statements(global, caches, scope, statements)
}
/// # Main Entry-Point (`FnCallExpr`)
///
/// Evaluate a function call expression.
pub(crate) fn eval_fn_call_expr(
&self,
@ -1488,10 +1487,26 @@ impl Engine {
..
} = 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
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
.get_arg_value(global, caches, scope, this_ptr, &args[0])?
.0
@ -1504,15 +1519,17 @@ impl Engine {
let operands = &mut [&mut lhs, &mut rhs];
if let Some(func) =
get_builtin_binary_op_fn(op_token.as_ref().unwrap(), operands[0], operands[1])
if let Some((func, ctx)) =
get_builtin_binary_op_fn(op_token.clone(), operands[0], operands[1])
{
// Built-in found
let orig_level = global.level;
global.level += 1;
let global = &*RestoreOnDrop::lock(global, move |g| g.level = orig_level);
auto_restore! { let orig_level = global.level; global.level += 1 }
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);
}

View File

@ -14,10 +14,10 @@ use std::prelude::v1::*;
#[non_exhaustive]
pub enum CallableFunction {
/// 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,
/// and the rest passed by value.
Method(Shared<FnAny>),
Method(Shared<FnAny>, bool),
/// An iterator function.
Iterator(Shared<IteratorFn>),
/// A plugin function,
@ -136,6 +136,18 @@ impl CallableFunction {
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.
#[inline]
#[must_use]
@ -156,7 +168,7 @@ impl CallableFunction {
#[must_use]
pub fn get_native_fn(&self) -> Option<&Shared<FnAny>> {
match self {
Self::Pure(f) | Self::Method(f) => Some(f),
Self::Pure(f, ..) | Self::Method(f, ..) => Some(f),
Self::Iterator(..) | Self::Plugin(..) => None,
#[cfg(not(feature = "no_function"))]
@ -204,16 +216,16 @@ impl CallableFunction {
#[cfg(not(feature = "no_function"))]
impl From<crate::ast::ScriptFnDef> for CallableFunction {
#[inline(always)]
fn from(_func: crate::ast::ScriptFnDef) -> Self {
Self::Script(_func.into())
fn from(func: crate::ast::ScriptFnDef) -> Self {
Self::Script(func.into())
}
}
#[cfg(not(feature = "no_function"))]
impl From<Shared<crate::ast::ScriptFnDef>> for CallableFunction {
#[inline(always)]
fn from(_func: Shared<crate::ast::ScriptFnDef>) -> Self {
Self::Script(_func)
fn from(func: Shared<crate::ast::ScriptFnDef>) -> Self {
Self::Script(func)
}
}

View File

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

View File

@ -4,11 +4,11 @@ use super::call::FnCallArgs;
use crate::ast::FnCallHashes;
use crate::eval::{Caches, GlobalRuntimeState};
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::{
calc_fn_hash, reify, Dynamic, Engine, EvalContext, FuncArgs, Position, RhaiResult,
RhaiResultOf, StaticVec, VarDefInfo, ERR,
calc_fn_hash, Dynamic, Engine, EvalContext, FuncArgs, Position, RhaiResult, RhaiResultOf,
StaticVec, VarDefInfo, ERR,
};
use std::any::{type_name, TypeId};
#[cfg(feature = "no_std")]
@ -424,8 +424,7 @@ impl<'a> NativeCallContext<'a> {
let caches = &mut Caches::new();
let fn_name = fn_name.as_ref();
let op_token = Token::lookup_symbol_from_syntax(fn_name);
let op_token = op_token.as_ref();
let op_token = Token::lookup_symbol_from_syntax(fn_name).unwrap_or(NO_TOKEN);
let args_len = args.len();
global.level += 1;
@ -547,13 +546,16 @@ pub fn locked_write<T>(value: &Locked<T>) -> LockGuardMut<T> {
/// General Rust function trail object.
#[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.
#[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.
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.
#[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.
pub trait PluginFunction {
/// 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?
#[must_use]
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?
///
/// 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.
#![allow(non_snake_case)]
#![allow(unused_imports)]
#![allow(unused_mut)]
#![allow(unused_variables)]
use super::call::FnCallArgs;
use super::callable_function::CallableFunction;
use super::native::{SendSync, Shared};
use crate::types::dynamic::{DynamicWriteLock, Variant};
use crate::{reify, Dynamic, NativeCallContext, RhaiResultOf};
use crate::{Dynamic, Identifier, NativeCallContext, RhaiResultOf};
#[cfg(feature = "no_std")]
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
/// of function parameter types in order to make each trait implementation unique.
@ -19,13 +25,13 @@ use std::{any::TypeId, mem};
///
/// # 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.
pub struct Mut<T>(T);
@ -64,123 +70,137 @@ pub fn by_value<T: Variant + Clone>(data: &mut Dynamic) -> T {
///
/// # Type Parameters
///
/// * `ARGS` - 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.
pub trait RegisterNativeFunction<ARGS, RET, RESULT> {
/// * `A` - a tuple containing parameter types, with `&mut T` represented by `Mut<T>`.
/// * `N` - a constant generic containing the number of parameters, must be consistent with `ARGS`.
/// * `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`].
#[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.
#[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.
/// Exported under the `metadata` feature only.
#[cfg(feature = "metadata")]
#[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.
/// Exported under the `metadata` feature only.
#[cfg(feature = "metadata")]
#[inline(always)]
#[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.
/// Exported under the `metadata` feature only.
#[cfg(feature = "metadata")]
#[inline(always)]
#[must_use]
fn return_type_name() -> &'static str {
std::any::type_name::<RET>()
type_name::<R>()
}
}
const EXPECT_ARGS: &str = "arguments";
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")))]
if stringify!($abi) == "Method" && !$args.is_empty() {
let deny = match $args.len() {
#[cfg(not(feature = "no_index"))]
3 if $ctx.fn_name() == crate::engine::FN_IDX_SET && $args[0].is_read_only() => true,
#[cfg(not(feature = "no_object"))]
2 if $ctx.fn_name().starts_with(crate::engine::FN_SET)
&& $args[0].is_read_only() =>
{
true
}
_ => false,
};
if deny {
if stringify!($abi) == "Method" && $no_const && $args[0].is_read_only() {
return Err(crate::ERR::ErrorNonPureMethodCallOnConstant(
$ctx.fn_name().to_string(),
$fn_name.to_string(),
crate::Position::NONE,
)
.into());
}
}
};
}
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
// ^ number of parameters
// ^ function parameter generic type name (A, B, C etc.)
// ^ call argument(like A, *B, &mut C etc)
// ^ function parameter marker type (A, Ref<B> or Mut<C>)
// ^ function parameter actual type (A, &B or &mut C)
// ^ argument let statement
// ^ parameter access function (by_value or by_ref)
impl<
FN: Fn($($param),*) -> RET + SendSync + 'static,
$($par: Variant + Clone,)*
RET: Variant + Clone
> RegisterNativeFunction<($($mark,)*), RET, ()> for FN {
#[inline(always)] fn param_types() -> Box<[TypeId]> { vec![$(TypeId::of::<$par>()),*].into_boxed_slice() }
#[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 return_type() -> TypeId { TypeId::of::<RET>() }
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
CallableFunction::$abi(Shared::new(move |_ctx: NativeCallContext, args: &mut FnCallArgs| {
RET: Variant + Clone,
> RegisterNativeFunction<($($mark,)*), $n, false, RET, false> for FN {
#[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] }
#[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] }
#[inline(always)] fn into_callable_function(self, fn_name: Identifier, no_const: bool) -> CallableFunction {
CallableFunction::$abi(Shared::new(move |_, args: &mut FnCallArgs| {
// 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 $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )*
let mut drain = args.iter_mut();
$(let mut $par = $clone(drain.next().unwrap()); )*
// Call the function with each argument value
let r = self($($arg),*);
// Map the result
Ok(Dynamic::from(r))
}))
}), false)
}
}
impl<
FN: for<'a> Fn(NativeCallContext<'a>, $($param),*) -> RET + SendSync + 'static,
$($par: Variant + Clone,)*
RET: Variant + Clone
> RegisterNativeFunction<($($mark,)*), RET, NativeCallContext<'static>> for FN {
#[inline(always)] fn param_types() -> Box<[TypeId]> { vec![$(TypeId::of::<$par>()),*].into_boxed_slice() }
#[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 return_type() -> TypeId { TypeId::of::<RET>() }
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
CallableFunction::$abi(Shared::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
// The arguments are assumed to be of the correct number and types!
check_constant!($abi, ctx, args);
RET: Variant + Clone,
> RegisterNativeFunction<($($mark,)*), $n, true, RET, false> for FN {
#[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] }
#[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] }
#[inline(always)] fn into_callable_function(self, fn_name: Identifier, no_const: bool) -> CallableFunction {
CallableFunction::$abi(Shared::new(move |ctx: Option<NativeCallContext>, args: &mut FnCallArgs| {
let ctx = ctx.unwrap();
let mut _drain = args.iter_mut();
$($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )*
// The arguments are assumed to be of the correct number and types!
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
let r = self(ctx, $($arg),*);
// Map the result
Ok(Dynamic::from(r))
}))
}), true)
}
}
@ -188,22 +208,21 @@ macro_rules! def_register {
FN: Fn($($param),*) -> RhaiResultOf<RET> + SendSync + 'static,
$($par: Variant + Clone,)*
RET: Variant + Clone
> RegisterNativeFunction<($($mark,)*), RET, RhaiResultOf<()>> for FN {
#[inline(always)] fn param_types() -> Box<[TypeId]> { vec![$(TypeId::of::<$par>()),*].into_boxed_slice() }
#[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 return_type() -> TypeId { TypeId::of::<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) -> CallableFunction {
CallableFunction::$abi(Shared::new(move |_ctx: NativeCallContext, args: &mut FnCallArgs| {
> RegisterNativeFunction<($($mark,)*), $n, false, RET, true> for FN {
#[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] }
#[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { type_name::<RhaiResultOf<RET>>() }
#[inline(always)] fn into_callable_function(self, fn_name: Identifier, no_const: bool) -> CallableFunction {
CallableFunction::$abi(Shared::new(move |_, args: &mut FnCallArgs| {
// 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 $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )*
let mut drain = args.iter_mut();
$(let mut $par = $clone(drain.next().unwrap()); )*
// Call the function with each argument value
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,
$($par: Variant + Clone,)*
RET: Variant + Clone
> RegisterNativeFunction<($($mark,)*), RET, RhaiResultOf<NativeCallContext<'static>>> for FN {
#[inline(always)] fn param_types() -> Box<[TypeId]> { vec![$(TypeId::of::<$par>()),*].into_boxed_slice() }
#[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 return_type() -> TypeId { TypeId::of::<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) -> CallableFunction {
CallableFunction::$abi(Shared::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
// The arguments are assumed to be of the correct number and types!
check_constant!($abi, ctx, args);
> RegisterNativeFunction<($($mark,)*), $n, true, RET, true> for FN {
#[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] }
#[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { type_name::<RhaiResultOf<RET>>() }
#[inline(always)] fn into_callable_function(self, fn_name: Identifier, no_const: bool) -> CallableFunction {
CallableFunction::$abi(Shared::new(move |ctx: Option<NativeCallContext>, args: &mut FnCallArgs| {
let ctx = ctx.unwrap();
let mut _drain = args.iter_mut();
$($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )*
// The arguments are assumed to be of the correct number and types!
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
self(ctx, $($arg),*).map(Dynamic::from)
}))
}), true)
}
}
//def_register!(imp_pop $($par => $mark => $param),*);
};
($p0:ident $(, $p:ident)*) => {
def_register!(imp Pure : $p0 => $p0 => $p0 => $p0 => let $p0 => by_value $(, $p => $p => $p => $p => let $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)*);
($p0:ident:$n0:expr $(, $p:ident: $n:expr)*) => {
def_register!(imp Pure : $n0 ; $p0 => $p0 => $p0 => $p0 => by_value $(, $p => $p => $p => $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
// ^ first parameter passed through
// ^ number of arguments ^ first parameter passed through
// ^ others passed by value (by_value)
// 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
//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 crate::ast::ScriptFnDef;
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;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -33,32 +33,6 @@ impl Engine {
rewind_scope: bool,
pos: Position,
) -> 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());
self.track_operation(global, pos)?;
@ -69,7 +43,7 @@ impl Engine {
}
#[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);
}
#[cfg(not(feature = "debugging"))]
@ -96,15 +70,14 @@ impl Engine {
// Push a new call stack frame
#[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();
global.debugger_mut().push_call_stack_frame(
fn_def.name.clone(),
scope.iter().skip(orig_scope_len).map(|(.., v)| v).collect(),
source,
pos,
);
global
.debugger_mut()
.push_call_stack_frame(fn_name, args, source, pos);
}
// Merge in encapsulated environment, if any
@ -131,37 +104,42 @@ impl Engine {
};
#[cfg(feature = "debugging")]
if self.debugger.is_some() {
if self.is_debugger_registered() {
let node = crate::ast::Stmt::Noop(fn_def.body.position());
self.run_debugger(global, caches, scope, this_ptr, &node)?;
}
// 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)
.or_else(|err| match *err {
// Convert return statement to return value
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
mut err if err.is_system_exception() => {
err.set_position(pos);
Err(err.into())
}
// 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")]
if self.debugger.is_some() {
if self.is_debugger_registered() {
let trigger = match global.debugger_mut().status {
crate::eval::DebuggerStatus::FunctionExit(n) => n >= global.level,
crate::eval::DebuggerStatus::Next(.., true) => true,
@ -236,7 +214,9 @@ impl Engine {
// Then check imported modules
global.contains_qualified_fn(hash_script)
// 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) {
// Do not cache "one-hit wonders"

View File

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

View File

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

View File

@ -170,7 +170,7 @@ impl FileModuleResolver {
#[inline(always)]
#[must_use]
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.
#[inline(always)]

View File

@ -24,6 +24,9 @@ use std::{
mem,
};
/// Standard not operator.
const OP_NOT: &str = Token::Bang.literal_syntax();
/// Level of optimization performed.
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
#[non_exhaustive]
@ -65,18 +68,19 @@ struct OptimizerState<'a> {
}
impl<'a> OptimizerState<'a> {
/// Create a new State.
/// Create a new [`OptimizerState`].
#[inline(always)]
pub fn new(
engine: &'a Engine,
#[cfg(not(feature = "no_function"))] lib: &'a [crate::SharedModule],
lib: &'a [crate::SharedModule],
optimization_level: OptimizationLevel,
) -> Self {
let mut _global = GlobalRuntimeState::new(engine);
let _lib = lib;
#[cfg(not(feature = "no_function"))]
{
_global.lib = lib.iter().cloned().collect();
_global.lib = _lib.iter().cloned().collect();
}
Self {
@ -138,7 +142,7 @@ impl<'a> OptimizerState<'a> {
pub fn call_fn_with_constant_arguments(
&mut self,
fn_name: &str,
op_token: Option<&Token>,
op_token: Token,
arg_values: &mut [Dynamic],
) -> Dynamic {
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].
fn optimize_stmt_block(
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() => {
state.set_dirty();
*expr = Expr::StringConstant(state.engine.get_interned_string(""), *pos);
*expr = Expr::StringConstant(state.engine.const_empty_string(), *pos);
}
// `... ${const} ...`
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);
},
// !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!
Expr::FnCall(x, ..) if x.name == KEYWORD_EVAL => {
state.propagate_constants = false;
@ -1150,10 +1135,14 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
return;
}
// 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 let Some(result) = get_builtin_binary_op_fn(x.op_token.as_ref().unwrap(), &arg_values[0], &arg_values[1])
.and_then(|f| {
let context = (state.engine, x.name.as_str(),None, &state.global, *pos).into();
_ 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.clone(), &arg_values[0], &arg_values[1])
.and_then(|(f, ctx)| {
let context = if ctx {
Some((state.engine, x.name.as_str(),None, &state.global, *pos).into())
} else {
None
};
let (first, second) = arg_values.split_first_mut().unwrap();
(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(),
#[cfg(not(feature = "no_closure"))]
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() {
@ -1260,14 +1249,45 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
}
}
impl Engine {
/// Has a system function a Rust-native override?
fn has_native_fn_override(&self, 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 self
.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 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,
engine: &Engine,
scope: &Scope,
#[cfg(not(feature = "no_function"))] lib: &[crate::SharedModule],
lib: &[crate::SharedModule],
optimization_level: OptimizationLevel,
) -> StmtBlockContainer {
let mut statements = statements;
@ -1279,20 +1299,10 @@ fn optimize_top_level(
}
// Set up the state
let mut state = OptimizerState::new(
engine,
#[cfg(not(feature = "no_function"))]
lib,
optimization_level,
);
let mut state = OptimizerState::new(self, lib, optimization_level);
// Add constants from global modules
for (name, value) in engine
.global_modules
.iter()
.rev()
.flat_map(|m| m.iter_var())
{
for (name, value) in self.global_modules.iter().rev().flat_map(|m| m.iter_var()) {
state.push_var(name, AccessMode::ReadOnly, value.clone());
}
@ -1308,9 +1318,9 @@ fn optimize_top_level(
optimize_stmt_block(statements, &mut state, true, false, true)
}
/// Optimize an [`AST`].
pub fn optimize_into_ast(
engine: &Engine,
/// 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<
@ -1350,7 +1360,7 @@ pub fn optimize_into_ast(
// Optimize the function body
let body = mem::take(&mut *fn_def.body);
*fn_def.body = optimize_top_level(body, engine, scope, lib2, optimization_level);
*fn_def.body = self.optimize_top_level(body, scope, lib2, optimization_level);
module.set_script_fn(fn_def);
}
@ -1362,22 +1372,20 @@ pub fn optimize_into_ast(
module.into()
};
#[cfg(feature = "no_function")]
let lib: crate::Shared<_> = crate::Module::new().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,
),
OptimizationLevel::Simple | OptimizationLevel::Full => {
self.optimize_top_level(statements, scope, &[lib.clone()], optimization_level)
}
},
#[cfg(not(feature = "no_function"))]
lib,
)
}
}

View File

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

View File

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

View File

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

View File

@ -220,7 +220,7 @@ fn collect_fn_metadata(
"comments".into(),
func.comments
.iter()
.map(|s| engine.get_interned_string(s.as_ref()).into())
.map(|s| engine.get_interned_string(s.as_str()).into())
.collect::<Array>()
.into(),
);
@ -267,9 +267,10 @@ fn collect_fn_metadata(
#[cfg(not(feature = "no_module"))]
ctx.engine()
.global_sub_modules
.iter()
.flat_map(|m| m.values())
.flat_map(|m| m.iter_script_fn())
.as_deref()
.into_iter()
.flatten()
.flat_map(|(_, m)| m.iter_script_fn())
.filter(|(ns, a, n, p, f)| filter(*ns, *a, n, *p, f))
.for_each(|(.., f)| {
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_ `=>` `Option<`_type_`>` `)`
/// * `reify!(`_variable_ or _expression_ `=>` _type_ `)`
#[macro_export]
macro_rules! reify {
($old:ident, |$new:ident : $t:ty| $code:expr, || $fallback:expr) => {{
#[allow(clippy::redundant_else)]

View File

@ -168,10 +168,12 @@ pub fn gen_metadata_to_json(
include_standard_packages: bool,
) -> serde_json::Result<String> {
let _ast = ast;
#[cfg(feature = "metadata")]
let mut global_doc = String::new();
let mut global = ModuleMetadata::new();
#[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());
}
@ -185,7 +187,16 @@ pub fn gen_metadata_to_json(
.global_modules
.iter()
.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| {
#[allow(unused_mut)]
let mut meta: FnMetadata = f.into();
@ -213,7 +224,17 @@ pub fn gen_metadata_to_json(
#[cfg(feature = "metadata")]
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)

View File

@ -5,7 +5,7 @@ use crate::engine::{
KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
};
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")]
use std::prelude::v1::*;
use std::{
@ -13,7 +13,6 @@ use std::{
char, fmt,
iter::{FusedIterator, Peekable},
num::NonZeroUsize,
ops::{Add, AddAssign},
rc::Rc,
str::{Chars, FromStr},
};
@ -24,9 +23,9 @@ pub struct TokenizerControlBlock {
/// Is the current tokenizer position within an interpolated text string?
/// This flag allows switching the tokenizer back to _text_ parsing after an interpolation stream.
pub is_within_text: bool,
/// Collection of global comments.
/// Global comments.
#[cfg(feature = "metadata")]
pub global_comments: Vec<SmartString>,
pub global_comments: String,
}
impl TokenizerControlBlock {
@ -37,7 +36,7 @@ impl TokenizerControlBlock {
Self {
is_within_text: false,
#[cfg(feature = "metadata")]
global_comments: Vec::new(),
global_comments: String::new(),
}
}
}
@ -50,326 +49,12 @@ type LERR = LexError;
/// Separator character for numbers.
const NUMBER_SEPARATOR: char = '_';
/// No token.
pub const NO_TOKEN: Token = Token::NONE;
/// A stream of tokens.
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.
/// Exported under the `internals` feature only.
#[derive(Debug, PartialEq, Clone, Hash)]
@ -489,6 +174,8 @@ pub enum Token {
For,
/// `in`
In,
/// `!in`
NotIn,
/// `<`
LessThan,
/// `>`
@ -575,7 +262,7 @@ pub enum Token {
/// A lexer error.
LexError(Box<LexError>),
/// A comment block.
Comment(Box<SmartString>),
Comment(Box<String>),
/// A reserved symbol.
Reserved(Box<SmartString>),
/// A custom keyword.
@ -584,7 +271,10 @@ pub enum Token {
#[cfg(not(feature = "no_custom_syntax"))]
Custom(Box<SmartString>),
/// End of the input stream.
/// Used as a placeholder for the end of input.
EOF,
/// Placeholder to indicate the lack of a token.
NONE,
}
impl fmt::Display for Token {
@ -610,6 +300,7 @@ impl fmt::Display for Token {
Comment(s) => f.write_str(s),
EOF => f.write_str("{EOF}"),
NONE => f.write_str("{NONE}"),
token => f.write_str(token.literal_syntax()),
}
@ -638,7 +329,7 @@ impl Token {
Custom(..) => false,
LexError(..) | Comment(..) => false,
EOF => false,
EOF | NONE => false,
_ => true,
}
@ -696,6 +387,7 @@ impl Token {
Loop => "loop",
For => "for",
In => "in",
NotIn => "!in",
LessThan => "<",
GreaterThan => ">",
Bang => "!",
@ -750,37 +442,43 @@ impl Token {
#[inline]
#[must_use]
pub const fn is_op_assignment(&self) -> bool {
#[allow(clippy::enum_glob_use)]
use Token::*;
matches!(
self,
Self::PlusAssign
| Self::MinusAssign
| Self::MultiplyAssign
| Self::DivideAssign
| Self::LeftShiftAssign
| Self::RightShiftAssign
| Self::ModuloAssign
| Self::PowerOfAssign
| Self::AndAssign
| Self::OrAssign
| Self::XOrAssign
PlusAssign
| MinusAssign
| MultiplyAssign
| DivideAssign
| LeftShiftAssign
| RightShiftAssign
| ModuloAssign
| PowerOfAssign
| AndAssign
| OrAssign
| XOrAssign
)
}
/// Get the corresponding operator of the token if it is an op-assignment operator.
#[must_use]
pub const fn get_base_op_from_assignment(&self) -> Option<Self> {
#[allow(clippy::enum_glob_use)]
use Token::*;
Some(match self {
Self::PlusAssign => Self::Plus,
Self::MinusAssign => Self::Minus,
Self::MultiplyAssign => Self::Multiply,
Self::DivideAssign => Self::Divide,
Self::LeftShiftAssign => Self::LeftShift,
Self::RightShiftAssign => Self::RightShift,
Self::ModuloAssign => Self::Modulo,
Self::PowerOfAssign => Self::PowerOf,
Self::AndAssign => Self::Ampersand,
Self::OrAssign => Self::Pipe,
Self::XOrAssign => Self::XOr,
PlusAssign => Plus,
MinusAssign => Minus,
MultiplyAssign => Multiply,
DivideAssign => Divide,
LeftShiftAssign => LeftShift,
RightShiftAssign => RightShift,
ModuloAssign => Modulo,
PowerOfAssign => PowerOf,
AndAssign => Ampersand,
OrAssign => Pipe,
XOrAssign => XOr,
_ => return None,
})
}
@ -789,37 +487,42 @@ impl Token {
#[inline]
#[must_use]
pub const fn has_op_assignment(&self) -> bool {
#[allow(clippy::enum_glob_use)]
use Token::*;
matches!(
self,
Self::Plus
| Self::Minus
| Self::Multiply
| Self::Divide
| Self::LeftShift
| Self::RightShift
| Self::Modulo
| Self::PowerOf
| Self::Ampersand
| Self::Pipe
| Self::XOr
Plus | Minus
| Multiply
| Divide
| LeftShift
| RightShift
| Modulo
| PowerOf
| Ampersand
| Pipe
| XOr
)
}
/// Get the corresponding op-assignment operator of the token.
#[must_use]
pub const fn convert_to_op_assignment(&self) -> Option<Self> {
#[allow(clippy::enum_glob_use)]
use Token::*;
Some(match self {
Self::Plus => Self::PlusAssign,
Self::Minus => Self::MinusAssign,
Self::Multiply => Self::MultiplyAssign,
Self::Divide => Self::DivideAssign,
Self::LeftShift => Self::LeftShiftAssign,
Self::RightShift => Self::RightShiftAssign,
Self::Modulo => Self::ModuloAssign,
Self::PowerOf => Self::PowerOfAssign,
Self::Ampersand => Self::AndAssign,
Self::Pipe => Self::OrAssign,
Self::XOr => Self::XOrAssign,
Plus => PlusAssign,
Minus => MinusAssign,
Multiply => MultiplyAssign,
Divide => DivideAssign,
LeftShift => LeftShiftAssign,
RightShift => RightShiftAssign,
Modulo => ModuloAssign,
PowerOf => PowerOfAssign,
Ampersand => AndAssign,
Pipe => OrAssign,
XOr => XOrAssign,
_ => return None,
})
}
@ -871,6 +574,7 @@ impl Token {
"loop" => Loop,
"for" => For,
"in" => In,
"!in" => NotIn,
"<" => LessThan,
">" => GreaterThan,
"!" => 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
/// (not sure about `fn` name).
#[must_use]
@ -1018,6 +715,7 @@ impl Token {
While |
Until |
In |
NotIn |
And |
AndAssign |
Or |
@ -1049,7 +747,7 @@ impl Token {
EqualsTo | NotEqualsTo => 90,
In => 110,
In | NotIn => 110,
LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo => 130,
@ -1170,7 +868,7 @@ impl From<Token> for String {
#[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct TokenizeState {
/// 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?
pub next_token_cannot_be_unary: bool,
/// Shared object to allow controlling the tokenizer externally.
@ -1197,6 +895,18 @@ pub trait InputStream {
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.
/// Exported under the `internals` feature only.
///
@ -1286,11 +996,8 @@ pub fn parse_string_literal(
break;
}
if let Some(max) = state.max_string_size {
if result.len() > max.get() {
return Err((LexError::StringTooLong(max.get()), *pos));
}
}
ensure_string_len_within_limit(state.max_string_len, &result)
.map_err(|err| (err, start))?;
// Close wrapper
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 {
if result.len() > max.get() {
return Err((LexError::StringTooLong(max.get()), *pos));
}
}
ensure_string_len_within_limit(state.max_string_len, &result).map_err(|err| (err, start))?;
Ok((result, interpolated, first_char))
}
@ -1446,7 +1149,7 @@ fn scan_block_comment(
stream: &mut impl InputStream,
level: usize,
pos: &mut Position,
comment: Option<&mut SmartString>,
comment: Option<&mut String>,
) -> usize {
let mut level = level;
let mut comment = comment;
@ -1541,7 +1244,7 @@ fn get_next_token_inner(
if state.comment_level > 0 {
let start_pos = *pos;
let mut comment = if state.include_comments {
Some(SmartString::new_const())
Some(String::new())
} else {
None
};
@ -1748,11 +1451,17 @@ fn get_next_token_inner(
// letter or underscore ...
#[cfg(not(feature = "unicode-xid-ident"))]
('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")]
(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
@ -1928,7 +1637,7 @@ fn get_next_token_inner(
('/', '/') => {
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(feature = "metadata")]
Some('/') => {
@ -1971,11 +1680,13 @@ fn get_next_token_inner(
if let Some(comment) = comment {
match comment {
#[cfg(feature = "metadata")]
_ if comment.starts_with("//!") => state
.tokenizer_control
.borrow_mut()
.global_comments
.push(comment),
_ if comment.starts_with("//!") => {
let g = &mut state.tokenizer_control.borrow_mut().global_comments;
if !g.is_empty() {
g.push('\n');
}
g.push_str(&comment);
}
_ => return Some((Token::Comment(comment.into()), start_pos)),
}
}
@ -1984,7 +1695,7 @@ fn get_next_token_inner(
state.comment_level = 1;
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(feature = "metadata")]
Some('*') => {
@ -2119,6 +1830,15 @@ fn get_next_token_inner(
}
('>', ..) => 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);
@ -2220,12 +1940,12 @@ fn get_next_token_inner(
}
/// Get the next token, parsing it as an identifier.
fn get_token_as_identifier(
fn parse_identifier_token(
stream: &mut impl InputStream,
pos: &mut Position,
start_pos: Position,
first_char: char,
) -> (Token, Position) {
) -> Result<(Token, Position), LexError> {
let mut identifier = SmartString::new_const();
identifier.push(first_char);
@ -2240,19 +1960,20 @@ fn get_token_as_identifier(
}
if let Some(token) = Token::lookup_symbol_from_syntax(&identifier) {
return (token, start_pos);
} else if Token::is_reserved_keyword(&identifier) {
return (Token::Reserved(Box::new(identifier)), start_pos);
return Ok((token, start_pos));
}
if Token::is_reserved_keyword(&identifier) {
return Ok((Token::Reserved(Box::new(identifier)), start_pos));
}
if !is_valid_identifier(&identifier) {
return (
return Ok((
Token::LexError(LERR::MalformedIdentifier(identifier.to_string()).into()),
start_pos,
);
));
}
(Token::Identifier(identifier.into()), start_pos)
Ok((Token::Identifier(identifier.into()), start_pos))
}
/// Is a keyword allowed as a function?
@ -2435,7 +2156,7 @@ impl<'a> Iterator for TokenIterator<'a> {
Some((Token::Reserved(s), pos)) => (match
(s.as_str(),
#[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")]
false
)
@ -2472,7 +2193,7 @@ impl<'a> Iterator for TokenIterator<'a> {
#[cfg(feature = "no_custom_syntax")]
(.., true) => unreachable!("no custom operators"),
// 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"});
Token::LexError(LERR::ImproperSymbol(s.to_string(), msg).into())
},
@ -2481,13 +2202,13 @@ impl<'a> Iterator for TokenIterator<'a> {
}, pos),
// Custom keyword
#[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)
}
// Custom keyword/symbol - must be disabled
#[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())) => {
if self.engine.disabled_symbols.as_ref().map_or(false,|m| m.contains(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_deref().map_or(false,|m| m.contains(token.literal_syntax())) {
// Disabled standard keyword/symbol
(Token::Custom(Box::new(token.literal_syntax().into())), pos)
} else {
@ -2496,7 +2217,7 @@ impl<'a> Iterator for TokenIterator<'a> {
}
}
// 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)
}
// Normal symbol
@ -2554,10 +2275,7 @@ impl Engine {
TokenIterator {
engine: self,
state: TokenizeState {
#[cfg(not(feature = "unchecked"))]
max_string_size: self.limits.max_string_size,
#[cfg(feature = "unchecked")]
max_string_size: None,
max_string_len: NonZeroUsize::new(self.max_string_size()),
next_token_cannot_be_unary: false,
tokenizer_control: buffer,
comment_level: 0,

View File

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

View File

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

View File

@ -135,22 +135,20 @@ impl fmt::Display for EvalAltResult {
#[cfg(not(feature = "no_function"))]
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() {
write!(f, " @ '{src}'")?;
}
}
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() {
write!(f, " @ '{src}'")?;
}
}
Self::ErrorInModule(s, err, ..) if s.is_empty() => {
write!(f, "Error in module > {err}")?
}
Self::ErrorInModule(s, err, ..) => write!(f, "Error in module '{s}' > {err}")?,
Self::ErrorInModule(s, err, ..) if s.is_empty() => write!(f, "{err}\nin module")?,
Self::ErrorInModule(s, err, ..) => write!(f, "{err}\nin module '{s}'")?,
Self::ErrorVariableExists(s, ..) => write!(f, "Variable already defined: {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::ErrorFunctionNotFound(s, ..) => write!(f, "Function not found: {s}")?,
Self::ErrorModuleNotFound(s, ..) => write!(f, "Module not found: {s}")?,
Self::ErrorDataRace(s, ..) => {
write!(f, "Data race detected when accessing variable: {s}")?
}
Self::ErrorDataRace(s, ..) => write!(f, "Data race detected on variable '{s}'")?,
Self::ErrorDotExpr(s, ..) if s.is_empty() => f.write_str("Malformed dot expression")?,
Self::ErrorDotExpr(s, ..) => f.write_str(s)?,
Self::ErrorIndexingType(s, ..) => write!(f, "Indexer unavailable: {s}")?,
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::ErrorTooManyModules(..) => f.write_str("Too many modules imported")?,
Self::ErrorStackOverflow(..) => f.write_str("Stack overflow")?,
@ -188,31 +184,25 @@ impl fmt::Display for EvalAltResult {
if s.starts_with(crate::engine::FN_GET) =>
{
let prop = &s[crate::engine::FN_GET.len()..];
write!(
f,
"Property {prop} is not pure and cannot be accessed on a constant"
)?
write!(f, "Non-pure property {prop} cannot be accessed on constant")?
}
#[cfg(not(feature = "no_object"))]
Self::ErrorNonPureMethodCallOnConstant(s, ..)
if s.starts_with(crate::engine::FN_SET) =>
{
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"))]
Self::ErrorNonPureMethodCallOnConstant(s, ..) if s == crate::engine::FN_IDX_GET => {
write!(
f,
"Indexer is not pure and cannot be accessed on a constant"
)?
write!(f, "Non-pure indexer cannot be accessed on constant")?
}
#[cfg(not(feature = "no_index"))]
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, ..) => {
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}")?,
@ -230,8 +220,8 @@ impl fmt::Display for EvalAltResult {
Self::ErrorArithmetic(s, ..) if s.is_empty() => f.write_str("Arithmetic error")?,
Self::ErrorArithmetic(s, ..) => f.write_str(s)?,
Self::LoopBreak(true, ..) => f.write_str("'break' must be inside a loop")?,
Self::LoopBreak(false, ..) => f.write_str("'continue' 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 within a loop")?,
Self::Return(..) => f.write_str("NOT AN ERROR - function returns value")?,
@ -261,7 +251,7 @@ impl fmt::Display for EvalAltResult {
f,
"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(" "))?,
}

View File

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

View File

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

View File

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

View File

@ -1,5 +1,8 @@
//! Module defining Rhai data types.
#[macro_use]
pub mod restore;
pub mod bloom_filter;
pub mod custom_types;
pub mod dynamic;
@ -9,7 +12,8 @@ pub mod fn_ptr;
pub mod immutable_string;
pub mod interner;
pub mod parse_error;
pub mod restore;
pub mod position;
pub mod position_none;
pub mod scope;
pub mod variant;
@ -25,6 +29,12 @@ pub use fn_ptr::FnPtr;
pub use immutable_string::ImmutableString;
pub use interner::StringsInterner;
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 scope::Scope;
pub use variant::Variant;

View File

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

View File

@ -386,7 +386,7 @@ impl Scope<'_> {
#[inline(always)]
pub fn pop(&mut self) -> &mut Self {
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
}

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[-3]")?, 1);
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]; 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();
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_eq!(handler.on_event("start", 999).as_int().unwrap(), 1041);

View File

@ -75,10 +75,16 @@ fn test_comments_doc() -> Result<(), Box<EvalAltResult>> {
",
)?;
#[cfg(not(feature = "no_position"))]
assert_eq!(
ast.iter_functions().next().unwrap().comments[0],
"/** 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
.compile(

View File

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

View File

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

View File

@ -34,6 +34,15 @@ fn test_loop() -> Result<(), Box<EvalAltResult>> {
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!(
*engine
.compile("let x = 0; if x > 0 { continue; }")